1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - 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.
26 //#include "resource.h"
27 #include "..\TortoiseShell\resource.h"
28 //#include "git_config.h"
29 #include "GitStatus.h"
30 #include "UnicodeUtils.h"
31 //#include "GitGlobal.h"
32 //#include "GitHelpers.h"
35 //# include "MessageBox.h"
36 //# include "registry.h"
37 //# include "TGitPath.h"
38 //# include "PathUtils.h"
42 #include "shellcache.h"
44 CGitIndexFileMap g_IndexFileMap
;
45 CGitHeadFileMap g_HeadFileMap
;
46 CGitIgnoreList g_IgnoreList
;
48 GitStatus::GitStatus(bool * pbCanceled
)
52 m_pool
= git_pool_create (NULL
);
54 git_error_clear(git_client_create_context(&ctx
, m_pool
));
58 ctx
->cancel_func
= cancel
;
59 ctx
->cancel_baton
= pbCanceled
;
63 git_error_clear(git_config_ensure(NULL
, m_pool
));
65 // set up authentication
66 m_prompt
.Init(m_pool
, ctx
);
68 // set up the configuration
69 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
73 ::MessageBox(NULL
, this->GetLastErrorMsg(), _T("TortoiseGit"), MB_ICONERROR
);
74 git_error_clear(m_err
);
75 git_pool_destroy (m_pool
); // free the allocated memory
79 // set up the Git_SSH param
80 CString tgit_ssh
= CRegString(_T("Software\\TortoiseGit\\SSH"));
81 if (tgit_ssh
.IsEmpty())
82 tgit_ssh
= CPathUtils::GetAppDirectory() + _T("TortoisePlink.exe");
83 tgit_ssh
.Replace('\\', '/');
84 if (!tgit_ssh
.IsEmpty())
86 git_config_t
* cfg
= (git_config_t
*)apr_hash_get ((apr_hash_t
*)ctx
->config
, Git_CONFIG_CATEGORY_CONFIG
,
88 git_config_set(cfg
, Git_CONFIG_SECTION_TUNNELS
, "ssh", CUnicodeUtils::GetUTF8(tgit_ssh
));
91 git_error_clear(git_config_ensure(NULL
, m_pool
));
93 // set up the configuration
94 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
100 GitStatus::~GitStatus(void)
103 git_error_clear(m_err
);
104 git_pool_destroy (m_pool
); // free the allocated memory
108 void GitStatus::ClearPool()
111 git_pool_clear(m_pool
);
116 CString
GitStatus::GetLastErrorMsg() const
118 // return Git::GetErrorString(m_err);
122 stdstring
GitStatus::GetLastErrorMsg() const
131 git_error_t
* ErrPtr
= m_err
;
134 msg
= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
138 /* Is this a Subversion-specific error code? */
139 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
140 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
141 msg
= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
142 /* Otherwise, this must be an APR error code. */
145 git_error_t
*temp_err
= NULL
;
146 const char * err_string
= NULL
;
147 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
150 git_error_clear (temp_err
);
151 msg
= _T("Can't recode error string from APR");
155 msg
= CUnicodeUtils::StdGetUnicode(err_string
);
161 while (ErrPtr
->child
)
163 ErrPtr
= ErrPtr
->child
;
167 msg
+= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
171 /* Is this a Subversion-specific error code? */
172 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
173 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
174 msg
+= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
175 /* Otherwise, this must be an APR error code. */
178 git_error_t
*temp_err
= NULL
;
179 const char * err_string
= NULL
;
180 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
183 git_error_clear (temp_err
);
184 msg
+= _T("Can't recode error string from APR");
188 msg
+= CUnicodeUtils::StdGetUnicode(err_string
);
195 } // if (m_err != NULL)
202 git_wc_status_kind
GitStatus::GetAllStatus(const CTGitPath
& path
, git_depth_t depth
)
204 git_wc_status_kind statuskind
;
205 // git_client_ctx_t * ctx;
207 // apr_pool_t * pool;
208 // git_error_t * err;
211 CString sProjectRoot
;
213 isDir
= path
.IsDirectory();
214 if (!path
.HasAdminDir(&sProjectRoot
))
215 return git_wc_status_none
;
217 // pool = git_pool_create (NULL); // create the memory pool
219 // git_error_clear(git_client_create_context(&ctx, pool));
221 // git_revnum_t youngest = Git_INVALID_REVNUM;
222 // git_opt_revision_t rev;
223 // rev.kind = git_opt_revision_unspecified;
224 statuskind
= git_wc_status_none
;
226 const BOOL bIsRecursive
= (depth
== git_depth_infinity
|| depth
== git_depth_unknown
); // taken from SVN source
229 CString s
= path
.GetWinPathString();
230 if (s
.GetLength() > sProjectRoot
.GetLength())
232 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == _T(':'))
233 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
235 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
240 #ifdef _TORTOISESHELL
241 isfull
= (g_ShellCache
.GetCacheType() == ShellCache::dllFull
);
243 isfull
= ((DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\CacheType"),
244 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
249 err
= GetDirStatus(sProjectRoot
,sSubPath
,&statuskind
, isfull
,bIsRecursive
,isfull
,NULL
, NULL
);
253 err
= GetFileStatus(sProjectRoot
,sSubPath
,&statuskind
,isfull
, false,isfull
, NULL
,NULL
);
260 git_wc_status_kind
GitStatus::GetAllStatusRecursive(const CTGitPath
& path
)
262 return GetAllStatus(path
, git_depth_infinity
);
266 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
268 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
272 // static private method
273 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
277 case git_wc_status_none
:
279 case git_wc_status_unversioned
:
281 case git_wc_status_ignored
:
283 case git_wc_status_incomplete
:
285 case git_wc_status_normal
:
286 case git_wc_status_external
:
288 case git_wc_status_added
:
290 case git_wc_status_missing
:
292 case git_wc_status_deleted
:
294 case git_wc_status_replaced
:
296 case git_wc_status_modified
:
298 case git_wc_status_merged
:
300 case git_wc_status_conflicted
:
302 case git_wc_status_obstructed
:
308 git_revnum_t
GitStatus::GetStatus(const CTGitPath
& path
, bool update
/* = false */, bool noignore
/* = false */, bool noexternals
/* = false */)
310 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
311 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
312 // after the call again
314 // apr_hash_t * statushash;
315 // apr_hash_t * exthash;
316 // apr_array_header_t * statusarray;
317 // const sort_item* item;
319 // git_error_clear(m_err);
320 // statushash = apr_hash_make(m_pool);
321 // exthash = apr_hash_make(m_pool);
322 git_revnum_t youngest
= GIT_INVALID_REVNUM
;
323 // git_opt_revision_t rev;
324 // rev.kind = git_opt_revision_unspecified;
326 CString sProjectRoot
;
327 if ( !path
.HasAdminDir(&sProjectRoot
) )
330 struct hashbaton_t hashbaton
;
331 // hashbaton.hash = statushash;
332 // hashbaton.exthash = exthash;
333 hashbaton
.pThis
= this;
337 #ifdef _TORTOISESHELL
338 isfull
= (g_ShellCache
.GetCacheType() == ShellCache::dllFull
);
340 isfull
= ((DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\CacheType"),
341 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
345 LPCTSTR lpszSubPath
= NULL
;
347 CString s
= path
.GetWinPathString();
348 if (s
.GetLength() > sProjectRoot
.GetLength())
350 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
351 lpszSubPath
= sSubPath
;
352 // skip initial slash if necessary
353 if (*lpszSubPath
== _T('\\'))
357 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
359 if(path
.IsDirectory())
361 m_err
= GetDirStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
, isfull
, false,!noignore
, NULL
, NULL
);
365 m_err
= GetFileStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
,isfull
, false,!noignore
, NULL
,NULL
);
369 // Error present if function is not under version control
370 if (m_err
) /*|| (apr_hash_count(statushash) == 0)*/
373 return GIT_INVALID_REVNUM
;
376 // Convert the unordered hash to an ordered, sorted array
377 /*statusarray = sort_hash (statushash,
378 sort_compare_items_as_paths,
381 // only the first entry is needed (no recurse)
382 // item = &APR_ARRAY_IDX (statusarray, 0, const sort_item);
384 // status = (git_wc_status2_t *) item->value;
389 // done to match TSVN functionality of this function (not sure if any code uses the reutrn val)
390 // if TGit does not need this, then change the return type of function
391 youngest
= g_Git
.GetHash(_T("HEAD"));
397 git_wc_status2_t
* GitStatus::GetFirstFileStatus(const CTGitPath
& path
, CTGitPath
& retPath
, bool update
, git_depth_t depth
, bool bNoIgnore
/* = true */, bool bNoExternals
/* = false */)
399 static git_wc_status2 st
;
403 m_fileCache.Init( CStringA( path.GetWinPathString().GetString() ) );
404 MessageBox(NULL, path.GetWinPathString(), _T("GetFirstFile"), MB_OK);
405 m_fileCache.m_pFileIter = m_fileCache.m_pFiles;
406 st.text_status = git_wc_status_none;
408 if (m_fileCache.m_pFileIter)
410 switch(m_fileCache.m_pFileIter->nStatus)
412 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
413 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
414 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
417 //retPath.SetFromGit((const char*)item->key);
419 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
425 const sort_item
* item
;
427 git_error_clear(m_err
);
428 m_statushash
= apr_hash_make(m_pool
);
429 m_externalhash
= apr_hash_make(m_pool
);
430 headrev
= Git_INVALID_REVNUM
;
431 git_opt_revision_t rev
;
432 rev
.kind
= git_opt_revision_unspecified
;
433 struct hashbaton_t hashbaton
;
434 hashbaton
.hash
= m_statushash
;
435 hashbaton
.exthash
= m_externalhash
;
436 hashbaton
.pThis
= this;
437 m_statushashindex
= 0;
438 m_err
= git_client_status4 (&headrev
,
439 path
.GetGitApiPath(m_pool
),
446 bNoIgnore
, //noignore
447 bNoExternals
, //noexternals
453 // Error present if function is not under version control
454 if ((m_err
!= NULL
) || (apr_hash_count(m_statushash
) == 0))
459 // Convert the unordered hash to an ordered, sorted array
460 m_statusarray
= sort_hash (m_statushash
,
461 sort_compare_items_as_paths
,
464 // only the first entry is needed (no recurse)
465 m_statushashindex
= 0;
466 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
467 retPath
.SetFromGit((const char*)item
->key
);
468 return (git_wc_status2_t
*) item
->value
;
474 unsigned int GitStatus::GetVersionedCount() const
476 // return /**/m_fileCache.GetFileCount();
478 unsigned int count
= 0;
480 const sort_item
* item
;
481 for (unsigned int i
=0; i
<apr_hash_count(m_statushash
); ++i
)
483 item
= &APR_ARRAY_IDX(m_statusarray
, i
, const sort_item
);
486 if (GitStatus::GetMoreImportant(((git_wc_status_t
*)item
->value
)->text_status
, git_wc_status_ignored
)!=git_wc_status_ignored
)
494 git_wc_status2_t
* GitStatus::GetNextFileStatus(CTGitPath
& retPath
)
496 static git_wc_status2 st
;
498 st
.text_status
= git_wc_status_none
;
500 /*if (m_fileCache.m_pFileIter)
502 switch(m_fileCache.m_pFileIter->nStatus)
504 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
505 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
506 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
509 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
515 const sort_item
* item
;
517 if ((m_statushashindex
+1) >= apr_hash_count(m_statushash
))
521 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
522 retPath
.SetFromGit((const char*)item
->key
);
523 return (git_wc_status2_t
*) item
->value
;
528 bool GitStatus::IsExternal(const CTGitPath
& path
) const
531 if (apr_hash_get(m_externalhash
, path
.GetGitApiPath(m_pool
), APR_HASH_KEY_STRING
))
537 bool GitStatus::IsInExternal(const CTGitPath
& path
) const
540 if (apr_hash_count(m_statushash
) == 0)
543 GitPool
localpool(m_pool
);
544 apr_hash_index_t
*hi
;
546 for (hi
= apr_hash_first(localpool
, m_externalhash
); hi
; hi
= apr_hash_next(hi
))
548 apr_hash_this(hi
, (const void**)&key
, NULL
, NULL
);
551 if (CTGitPath(CUnicodeUtils::GetUnicode(key
)).IsAncestorOf(path
))
560 void GitStatus::GetStatusString(git_wc_status_kind status
, size_t buflen
, TCHAR
* string
)
565 case git_wc_status_none
:
568 case git_wc_status_unversioned
:
569 buf
= _T("unversioned\0");
571 case git_wc_status_normal
:
572 buf
= _T("normal\0");
574 case git_wc_status_added
:
577 case git_wc_status_missing
:
578 buf
= _T("missing\0");
580 case git_wc_status_deleted
:
581 buf
= _T("deleted\0");
583 case git_wc_status_replaced
:
584 buf
= _T("replaced\0");
586 case git_wc_status_modified
:
587 buf
= _T("modified\0");
589 case git_wc_status_merged
:
590 buf
= _T("merged\0");
592 case git_wc_status_conflicted
:
593 buf
= _T("conflicted\0");
595 case git_wc_status_obstructed
:
596 buf
= _T("obstructed\0");
598 case git_wc_status_ignored
:
601 case git_wc_status_external
:
602 buf
= _T("external");
604 case git_wc_status_incomplete
:
605 buf
= _T("incomplete\0");
611 _stprintf_s(string
, buflen
, _T("%s"), buf
);
614 void GitStatus::GetStatusString(HINSTANCE hInst
, git_wc_status_kind status
, TCHAR
* string
, int size
, WORD lang
)
618 case git_wc_status_none
:
619 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
621 case git_wc_status_unversioned
:
622 LoadStringEx(hInst
, IDS_STATUSUNVERSIONED
, string
, size
, lang
);
624 case git_wc_status_normal
:
625 LoadStringEx(hInst
, IDS_STATUSNORMAL
, string
, size
, lang
);
627 case git_wc_status_added
:
628 LoadStringEx(hInst
, IDS_STATUSADDED
, string
, size
, lang
);
630 case git_wc_status_missing
:
631 LoadStringEx(hInst
, IDS_STATUSABSENT
, string
, size
, lang
);
633 case git_wc_status_deleted
:
634 LoadStringEx(hInst
, IDS_STATUSDELETED
, string
, size
, lang
);
636 case git_wc_status_replaced
:
637 LoadStringEx(hInst
, IDS_STATUSREPLACED
, string
, size
, lang
);
639 case git_wc_status_modified
:
640 LoadStringEx(hInst
, IDS_STATUSMODIFIED
, string
, size
, lang
);
642 case git_wc_status_merged
:
643 LoadStringEx(hInst
, IDS_STATUSMERGED
, string
, size
, lang
);
645 case git_wc_status_conflicted
:
646 LoadStringEx(hInst
, IDS_STATUSCONFLICTED
, string
, size
, lang
);
648 case git_wc_status_ignored
:
649 LoadStringEx(hInst
, IDS_STATUSIGNORED
, string
, size
, lang
);
651 case git_wc_status_obstructed
:
652 LoadStringEx(hInst
, IDS_STATUSOBSTRUCTED
, string
, size
, lang
);
654 case git_wc_status_external
:
655 LoadStringEx(hInst
, IDS_STATUSEXTERNAL
, string
, size
, lang
);
657 case git_wc_status_incomplete
:
658 LoadStringEx(hInst
, IDS_STATUSINCOMPLETE
, string
, size
, lang
);
661 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
667 CString
GitStatus::GetDepthString(git_depth_t depth
)
673 case git_depth_unknown
:
674 sDepth
.LoadString(IDS_Git_DEPTH_UNKNOWN
);
676 case git_depth_empty
:
677 sDepth
.LoadString(IDS_Git_DEPTH_EMPTY
);
679 case git_depth_files
:
680 sDepth
.LoadString(IDS_Git_DEPTH_FILES
);
682 case git_depth_immediates
:
683 sDepth
.LoadString(IDS_Git_DEPTH_IMMEDIATE
);
685 case git_depth_infinity
:
686 sDepth
.LoadString(IDS_Git_DEPTH_INFINITE
);
695 void GitStatus::GetDepthString(HINSTANCE hInst
, git_depth_t depth
, TCHAR
* string
, int size
, WORD lang
)
700 case git_depth_unknown
:
701 LoadStringEx(hInst
, IDS_SVN_DEPTH_UNKNOWN
, string
, size
, lang
);
703 case git_depth_empty
:
704 LoadStringEx(hInst
, IDS_SVN_DEPTH_EMPTY
, string
, size
, lang
);
706 case git_depth_files
:
707 LoadStringEx(hInst
, IDS_SVN_DEPTH_FILES
, string
, size
, lang
);
709 case git_depth_immediates
:
710 LoadStringEx(hInst
, IDS_SVN_DEPTH_IMMEDIATE
, string
, size
, lang
);
712 case git_depth_infinity
:
713 LoadStringEx(hInst
, IDS_SVN_DEPTH_INFINITE
, string
, size
, lang
);
720 int GitStatus::LoadStringEx(HINSTANCE hInstance
, UINT uID
, LPTSTR lpBuffer
, int nBufferMax
, WORD wLanguage
)
722 const STRINGRESOURCEIMAGE
* pImage
;
723 const STRINGRESOURCEIMAGE
* pImageEnd
;
729 HRSRC hResource
= FindResourceEx(hInstance
, RT_STRING
, MAKEINTRESOURCE(((uID
>>4)+1)), wLanguage
);
732 // try the default language before giving up!
733 hResource
= FindResource(hInstance
, MAKEINTRESOURCE(((uID
>>4)+1)), RT_STRING
);
737 hGlobal
= LoadResource(hInstance
, hResource
);
740 pImage
= (const STRINGRESOURCEIMAGE
*)::LockResource(hGlobal
);
744 nResourceSize
= ::SizeofResource(hInstance
, hResource
);
745 pImageEnd
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+nResourceSize
);
748 while ((iIndex
> 0) && (pImage
< pImageEnd
))
750 pImage
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+(sizeof(STRINGRESOURCEIMAGE
)+(pImage
->nLength
*sizeof(WCHAR
))));
753 if (pImage
>= pImageEnd
)
755 if (pImage
->nLength
== 0)
758 ret
= pImage
->nLength
;
759 if (pImage
->nLength
> nBufferMax
)
761 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
-1);
762 lpBuffer
[nBufferMax
-1] = 0;
766 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
);
772 BOOL
GitStatus::getallstatus(const struct wgFile_s
*pFile
, void *pUserData
)
774 git_wc_status_kind
* s
= (git_wc_status_kind
*)pUserData
;
775 *s
= GitStatus::GetMoreImportant(*s
, GitStatusFromWingit(pFile
->nStatus
));
779 BOOL
GitStatus::getstatus(const struct wgFile_s
*pFile
, void *pUserData
)
781 git_wc_status2_t
* s
= (git_wc_status2_t
*)pUserData
;
782 s
->prop_status
= s
->text_status
= GitStatus::GetMoreImportant(s
->prop_status
, GitStatusFromWingit(pFile
->nStatus
));
787 git_error_t
* GitStatus::getallstatus(void * baton
, const char * /*path*/, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
789 git_wc_status_kind
* s
= (git_wc_status_kind
*)baton
;
790 *s
= GitStatus::GetMoreImportant(*s
, status
->text_status
);
791 *s
= GitStatus::GetMoreImportant(*s
, status
->prop_status
);
797 git_error_t
* GitStatus::getstatushash(void * baton
, const char * path
, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
799 hashbaton_t
* hash
= (hashbaton_t
*)baton
;
800 const StdStrAVector
& filterList
= hash
->pThis
->m_filterFileList
;
801 if (status
->text_status
== git_wc_status_external
)
803 apr_hash_set (hash
->exthash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, (const void*)1);
806 if(filterList
.size() > 0)
808 // We have a filter active - we're only interested in files which are in
810 if(!binary_search(filterList
.begin(), filterList
.end(), path
))
812 // This item is not in the filter - don't store it
816 git_wc_status2_t
* statuscopy
= git_wc_dup_status2 (status
, hash
->pThis
->m_pool
);
817 apr_hash_set (hash
->hash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, statuscopy
);
821 apr_array_header_t
* GitStatus::sort_hash (apr_hash_t
*ht
,
822 int (*comparison_func
) (const GitStatus::sort_item
*, const GitStatus::sort_item
*),
825 apr_hash_index_t
*hi
;
826 apr_array_header_t
*ary
;
828 /* allocate an array with only one element to begin with. */
829 ary
= apr_array_make (pool
, 1, sizeof(sort_item
));
831 /* loop over hash table and push all keys into the array */
832 for (hi
= apr_hash_first (pool
, ht
); hi
; hi
= apr_hash_next (hi
))
834 sort_item
*item
= (sort_item
*)apr_array_push (ary
);
836 apr_hash_this (hi
, &item
->key
, &item
->klen
, &item
->value
);
839 /* now quick sort the array. */
840 qsort (ary
->elts
, ary
->nelts
, ary
->elt_size
,
841 (int (*)(const void *, const void *))comparison_func
);
846 int GitStatus::sort_compare_items_as_paths (const sort_item
*a
, const sort_item
*b
)
848 const char *astr
, *bstr
;
850 astr
= (const char*)a
->key
;
851 bstr
= (const char*)b
->key
;
852 return git_path_compare_paths (astr
, bstr
);
856 git_error_t
* GitStatus::cancel(void *baton
)
859 volatile bool * canceled
= (bool *)baton
;
863 temp
.LoadString(IDS_Git_USERCANCELLED
);
864 return git_error_create(Git_ERR_CANCELLED
, NULL
, CUnicodeUtils::GetUTF8(temp
));
873 // Set-up a filter to restrict the files which will have their status stored by a get-status
874 void GitStatus::SetFilter(const CTGitPathList
& fileList
)
876 m_filterFileList
.clear();
877 for(int fileIndex
= 0; fileIndex
< fileList
.GetCount(); fileIndex
++)
879 // m_filterFileList.push_back(fileList[fileIndex].GetGitApiPath(m_pool));
881 // Sort the list so that we can do binary searches
882 std::sort(m_filterFileList
.begin(), m_filterFileList
.end());
885 void GitStatus::ClearFilter()
887 m_filterFileList
.clear();
892 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
894 int GitStatus::GetFileStatus(CString
&gitdir
,CString
&path
,git_wc_status_kind
* status
,BOOL IsFull
, BOOL IsRecursive
,BOOL IsIgnore
, FIll_STATUS_CALLBACK callback
,void *pData
)
899 TCHAR oldpath
[MAX_PATH
+1];
900 memset(oldpath
,0,MAX_PATH
+1);
902 path
.Replace(_T('\\'),_T('/'));
906 git_wc_status_kind st
= git_wc_status_none
;
909 g_IndexFileMap
.GetFileStatus(gitdir
,path
,&st
,IsFull
,false,callback
,pData
,&hash
);
911 if( st
== git_wc_status_conflicted
)
915 callback(gitdir
+_T("/")+path
,st
,false,pData
);
919 if( st
== git_wc_status_unversioned
)
923 *status
= git_wc_status_unversioned
;
925 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
929 if( g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
931 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
933 if( g_IgnoreList
.IsIgnore(path
, gitdir
) )
935 st
= git_wc_status_ignored
;
939 callback(gitdir
+_T("/")+path
,st
, false, pData
);
944 if( st
== git_wc_status_normal
&& IsFull
)
947 int ret
=g_HeadFileMap
.CheckHeadUpdate(gitdir
);
950 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
953 CAutoReadLock
lock(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
954 b
= g_HeadFileMap
[gitdir
].m_Head
!= g_HeadFileMap
[gitdir
].m_TreeHash
;
959 g_HeadFileMap
[gitdir
].ReadHeadHash(gitdir
);
961 if( g_HeadFileMap
[gitdir
].m_HeadFile
.IsEmpty() )
963 *status
=st
=git_wc_status_added
;
965 callback(gitdir
+_T("/")+path
,st
,false,pData
);
968 if(g_HeadFileMap
[gitdir
].ReadTree())
970 g_HeadFileMap
[gitdir
].m_LastModifyTimeHead
= 0;
971 //Check if init repository
972 *status
= g_HeadFileMap
[gitdir
].m_Head
.IsEmpty()? git_wc_status_added
: st
;
974 callback(gitdir
+_T("/")+path
,*status
,false,pData
);
979 // Check Head Tree Hash;
981 CAutoReadLock
lock(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
984 std::map
<CString
,int>::iterator it
=g_HeadFileMap
[gitdir
].m_Map
.find(path
);
986 if(it
== g_HeadFileMap
[gitdir
].m_Map
.end())
988 *status
=st
=git_wc_status_added
;
989 ATLTRACE(_T("File miss in head tree %s"), path
);
991 callback(gitdir
+_T("/")+path
,st
,false, pData
);
995 //staged and not commit
996 if( g_HeadFileMap
[gitdir
].at(it
->second
).m_Hash
!= hash
)
998 *status
=st
=git_wc_status_modified
;
1000 callback(gitdir
+_T("/")+path
,st
, false, pData
);
1008 callback(gitdir
+_T("/")+path
,st
,false, pData
);
1015 *status
= git_wc_status_none
;
1023 int GitStatus::GetHeadHash(CString
&gitdir
, CGitHash
&hash
)
1025 return g_HeadFileMap
.GetHeadHash(gitdir
, hash
);
1028 bool GitStatus::IsGitReposChanged(CString
&gitdir
,CString
&subpaths
, int mode
)
1030 if( mode
& GIT_MODE_INDEX
)
1033 g_IndexFileMap
.CheckAndUpdateIndex(gitdir
,&load
);
1037 if( mode
& GIT_MODE_HEAD
)
1039 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1043 if( mode
& GIT_MODE_IGNORE
)
1045 if(g_IgnoreList
.CheckIgnoreChanged(gitdir
,subpaths
))
1051 int GitStatus::LoadIgnoreFile(CString
&gitdir
,CString
&subpaths
)
1053 return g_IgnoreList
.LoadAllIgnoreFile(gitdir
,subpaths
);
1055 int GitStatus::IsUnderVersionControl(CString
&gitdir
, CString
&path
, bool isDir
,bool *isVersion
)
1057 return g_IndexFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
1060 __int64
GitStatus::GetIndexFileTime(CString
&gitdir
)
1062 CAutoReadLock
lock(&g_IndexFileMap
.m_SharedMutex
);
1064 if(g_IndexFileMap
.find(gitdir
) == g_IndexFileMap
.end())
1067 return g_IndexFileMap
[gitdir
].m_LastModifyTime
;
1070 int GitStatus::GetIgnoreFileChangeTimeList(CString
&dir
, std::vector
<__int64
> &timelist
)
1072 return g_IgnoreList
.GetIgnoreFileChangeTimeList(dir
,timelist
);
1074 int GitStatus::IsIgnore(CString
&gitdir
, CString
&path
, bool *isIgnore
)
1076 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1077 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1079 *isIgnore
= g_IgnoreList
.IsIgnore(path
,gitdir
);
1084 int GitStatus::EnumDirStatus(CString
&gitdir
,CString
&subpath
,git_wc_status_kind
* status
,BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
, FIll_STATUS_CALLBACK callback
, void *pData
)
1088 TCHAR oldpath
[MAX_PATH
+1];
1089 memset(oldpath
,0,MAX_PATH
+1);
1091 CString path
=subpath
;
1093 path
.Replace(_T('\\'),_T('/'));
1095 if(path
[path
.GetLength()-1] != _T('/'))
1096 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1100 g_IndexFileMap
.CheckAndUpdateIndex(gitdir
);
1105 CAutoReadLock
lock(&g_IndexFileMap
.m_SharedMutex
);
1106 CAutoReadLock
lock1(&g_IndexFileMap
[gitdir
].m_SharedMutex
);
1108 pos
=SearchInSortVector(g_IndexFileMap
[gitdir
],path
.GetBuffer(),path
.GetLength());
1111 if(subpath
.IsEmpty() && pos
<0)
1112 { // for new init repository
1113 *status
= git_wc_status_normal
;
1115 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1119 //Not In Version Contorl
1124 *status
= git_wc_status_unversioned
;
1126 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1129 //Check ignore always.
1131 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1132 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1134 if(g_IgnoreList
.IsIgnore(path
,gitdir
))
1135 *status
= git_wc_status_ignored
;
1137 *status
= git_wc_status_unversioned
;
1139 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
1141 //Check init repository
1144 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1145 CAutoReadLock
lock1(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
1147 if(g_HeadFileMap
[gitdir
].m_Head
.IsEmpty() && path
.IsEmpty())
1148 *status
= git_wc_status_normal
;
1152 }else // In version control
1154 *status
= git_wc_status_normal
;
1156 g_IndexFileMap
.m_SharedMutex
.AcquireShared();
1157 g_IndexFileMap
[gitdir
].m_SharedMutex
.AcquireShared();
1164 end
=g_IndexFileMap
[gitdir
].size()-1;
1167 GetRangeInSortVector(g_IndexFileMap
[gitdir
],path
.GetBuffer(),path
.GetLength(),&start
,&end
,pos
);
1168 CGitIndexList::iterator it
;
1170 it
= g_IndexFileMap
[gitdir
].begin()+start
;
1172 CString sub
, currentPath
;
1174 for(int i
=start
;i
<=end
;i
++,it
++)
1176 if( ((*it
).m_Flags
& CE_STAGEMASK
) !=0)
1178 *status
= git_wc_status_conflicted
;
1183 //skip child directory
1184 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1188 currentPath
= (*it
).m_FileName
.Left(pos
);
1189 if( callback
&& (sub
!= currentPath
) )
1192 ATLTRACE(_T("index subdir %s\n"),sub
);
1195 g_IndexFileMap
[gitdir
].m_SharedMutex
.ReleaseShared();
1196 g_IndexFileMap
.m_SharedMutex
.ReleaseShared();
1197 callback(gitdir
+ _T("\\")+sub
, git_wc_status_normal
,true, pData
);
1198 g_IndexFileMap
.m_SharedMutex
.AcquireShared();
1199 g_IndexFileMap
[gitdir
].m_SharedMutex
.AcquireShared();
1206 git_wc_status_kind filestatus
= git_wc_status_none
;
1208 g_IndexFileMap
[gitdir
].m_SharedMutex
.ReleaseShared();
1209 g_IndexFileMap
.m_SharedMutex
.ReleaseShared();
1210 GetFileStatus(gitdir
,(*it
).m_FileName
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1211 g_IndexFileMap
.m_SharedMutex
.AcquireShared();
1212 g_IndexFileMap
[gitdir
].m_SharedMutex
.AcquireShared();
1214 *status
= max(filestatus
, *status
) ;
1217 //Check Miss file in index "git rm file"
1218 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
1220 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
) || g_HeadFileMap
.IsHashChanged(gitdir
))
1222 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1224 g_HeadFileMap
[gitdir
].ReadHeadHash(gitdir
);
1226 if(g_HeadFileMap
[gitdir
].ReadTree())
1229 g_Git
.SetCurrentDir(gitdir
);
1230 ::GetCurrentDirectory(MAX_PATH
,oldpath
);
1231 ::SetCurrentDirectory(g_Git
.m_CurrentDir
);
1233 g_HeadFileMap
[gitdir
].ReadTree();
1237 it
= ::g_IndexFileMap
[gitdir
].begin()+start
;
1240 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1241 CAutoReadLock
lock1(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
1243 //Check if new init repository
1244 if( g_HeadFileMap
[gitdir
].size() > 0 || g_HeadFileMap
[gitdir
].m_Head
.IsEmpty() )
1247 if( *status
== git_wc_status_normal
)
1249 pos
= SearchInSortVector(g_HeadFileMap
[gitdir
], path
.GetBuffer(), path
.GetLength());
1252 *status
= max(git_wc_status_added
, *status
) ;
1257 GetRangeInSortVector(g_HeadFileMap
[gitdir
],path
.GetBuffer(),path
.GetLength(),&hstart
,&hend
,pos
);
1258 CGitHeadFileList::iterator hit
;
1259 hit
= g_HeadFileMap
[gitdir
].begin()+hstart
;
1260 for(int i
=hstart
;i
<=hend
;i
++)
1262 if( ::g_IndexFileMap
[gitdir
].m_Map
.find((*hit
).m_FileName
) == g_IndexFileMap
[gitdir
].m_Map
.end())
1264 *status
=max(git_wc_status_deleted
, *status
);
1270 } // endif if( *status == git_wc_status_normal )
1271 }// endif if( g_HeadFileMap[gitdir].size() > 0 || g_HeadFileMap[gitdir].m_Head.IsEmpty() )
1275 g_IndexFileMap
[gitdir
].m_SharedMutex
.ReleaseShared();
1276 g_IndexFileMap
.m_SharedMutex
.ReleaseShared();
1278 }// end inversion control
1279 if(callback
) callback(gitdir
+_T("/")+subpath
,*status
,true, pData
);
1283 ::SetCurrentDirectory(oldpath
);
1287 *status
= git_wc_status_none
;
1293 int GitStatus::GetDirStatus(CString
&gitdir
,CString
&subpath
,git_wc_status_kind
* status
,BOOL IsFul
, BOOL IsRecursive
,BOOL IsIgnore
,FIll_STATUS_CALLBACK callback
,void *pData
)
1297 TCHAR oldpath
[MAX_PATH
+1];
1298 memset(oldpath
,0,MAX_PATH
+1);
1300 CString path
=subpath
;
1302 path
.Replace(_T('\\'),_T('/'));
1304 if(path
[path
.GetLength()-1] != _T('/'))
1305 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1309 g_IndexFileMap
.CheckAndUpdateIndex(gitdir
);
1311 CAutoReadLock
lock(&g_IndexFileMap
.m_SharedMutex
);
1312 CAutoReadLock
lock1(&g_IndexFileMap
[gitdir
].m_SharedMutex
);
1314 int pos
=SearchInSortVector(g_IndexFileMap
[gitdir
],path
.GetBuffer(),path
.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 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1347 CAutoReadLock
lock1(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
1348 //Check init repository
1349 if(g_HeadFileMap
[gitdir
].m_Head
.IsEmpty() && path
.IsEmpty())
1350 *status
= git_wc_status_normal
;
1353 }else // In version control
1355 *status
= git_wc_status_normal
;
1362 end
=g_IndexFileMap
[gitdir
].size()-1;
1364 GetRangeInSortVector(g_IndexFileMap
[gitdir
],path
.GetBuffer(),path
.GetLength(),&start
,&end
,pos
);
1365 CGitIndexList::iterator it
;
1367 it
= g_IndexFileMap
[gitdir
].begin()+start
;
1370 for(int i
=start
;i
<=end
;i
++)
1372 if( ((*it
).m_Flags
& CE_STAGEMASK
) !=0)
1374 *status
= git_wc_status_conflicted
;
1377 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1378 if(dirpos
<0 || IsRecursive
)
1379 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_conflicted
,false,pData
);
1386 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
1388 *status
= git_wc_status_normal
;
1390 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1392 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1393 CAutoReadLock
lock1(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
1395 g_HeadFileMap
[gitdir
].ReadHeadHash(gitdir
);
1397 if(g_HeadFileMap
[gitdir
].ReadTree())
1400 g_Git
.SetCurrentDir(gitdir
);
1401 ::GetCurrentDirectory(MAX_PATH
,oldpath
);
1402 ::SetCurrentDirectory(g_Git
.m_CurrentDir
);
1404 g_HeadFileMap
[gitdir
].ReadTree();
1408 it
= ::g_IndexFileMap
[gitdir
].begin()+start
;
1412 CAutoReadLock
lock(&g_HeadFileMap
.m_SharedMutex
);
1413 CAutoReadLock
lock1(&g_HeadFileMap
[gitdir
].m_SharedMutex
);
1416 //Check if new init repository
1417 if( g_HeadFileMap
[gitdir
].size() > 0 || g_HeadFileMap
[gitdir
].m_Head
.IsEmpty() )
1419 for(int i
=start
;i
<=end
;i
++)
1421 if( g_HeadFileMap
[gitdir
].m_Map
.find((*it
).m_FileName
) == g_HeadFileMap
[gitdir
].m_Map
.end())
1425 pos
= g_HeadFileMap
[gitdir
].m_Map
[(*it
).m_FileName
];
1429 *status
= *status
= max(git_wc_status_added
, *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_added
,false, pData
);
1440 if( pos
>=0 && g_HeadFileMap
[gitdir
][pos
].m_Hash
!= (*it
).m_IndexHash
)
1442 *status
= *status
= max(git_wc_status_modified
, *status
) ;
1445 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1446 if(dirpos
<0 || IsRecursive
)
1447 callback(gitdir
+_T("\\")+ it
->m_FileName
, git_wc_status_modified
,false, pData
);
1457 if( *status
== git_wc_status_normal
)
1459 pos
= SearchInSortVector(g_HeadFileMap
[gitdir
], path
.GetBuffer(), path
.GetLength());
1462 *status
= git_wc_status_added
;
1467 GetRangeInSortVector(g_HeadFileMap
[gitdir
],path
.GetBuffer(),path
.GetLength(),&hstart
,&hend
,pos
);
1468 CGitHeadFileList::iterator hit
;
1469 hit
= g_HeadFileMap
[gitdir
].begin()+start
;
1470 for(int i
=hstart
;i
<=hend
;i
++)
1472 if( ::g_IndexFileMap
[gitdir
].m_Map
.find((*hit
).m_FileName
) == g_IndexFileMap
[gitdir
].m_Map
.end())
1474 *status
= git_wc_status_deleted
;
1484 // If define callback, it need update each file status.
1485 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
1486 // because git_wc_status_conflicted is highest.s
1487 if(callback
|| (*status
!= git_wc_status_conflicted
))
1492 CString sub
, currentPath
;
1493 it
= g_IndexFileMap
[gitdir
].begin()+start
;
1494 for(int i
=start
;i
<=end
;i
++,it
++)
1498 //skip child directory
1499 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1503 currentPath
= (*it
).m_FileName
.Left(pos
);
1504 if( callback
&& (sub
!= currentPath
) )
1507 ATLTRACE(_T("index subdir %s\n"),sub
);
1508 if(callback
) callback(gitdir
+ _T("\\")+sub
,
1509 git_wc_status_normal
,true, pData
);
1515 git_wc_status_kind filestatus
= git_wc_status_none
;
1517 GetFileStatus(gitdir
,(*it
).m_FileName
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1519 *status
= max(filestatus
, *status
) ;
1525 if(callback
) callback(gitdir
+_T("/")+subpath
,*status
,true, pData
);
1529 ::SetCurrentDirectory(oldpath
);
1534 *status
= git_wc_status_none
;
1541 bool GitStatus::IsExistIndexLockFile(CString
&gitdir
)
1543 CString sDirName
= gitdir
;
1547 if(PathFileExists(sDirName
+ _T("\\.git")))
1549 if(PathFileExists(sDirName
+ _T("\\.git\\index.lock")))
1555 int x
= sDirName
.ReverseFind(_T('\\'));
1559 sDirName
= sDirName
.Left(x
);