1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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 extern CGitAdminDirMap g_AdminDirMap
;
41 CGitIndexFileMap g_IndexFileMap
;
42 CGitHeadFileMap g_HeadFileMap
;
43 CGitIgnoreList g_IgnoreList
;
45 GitStatus::GitStatus(bool * /*pbCanceled*/)
49 m_pool
= git_pool_create (NULL
);
51 git_error_clear(git_client_create_context(&ctx
, m_pool
));
55 ctx
->cancel_func
= cancel
;
56 ctx
->cancel_baton
= pbCanceled
;
60 git_error_clear(git_config_ensure(NULL
, m_pool
));
62 // set up authentication
63 m_prompt
.Init(m_pool
, ctx
);
65 // set up the configuration
66 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
70 ::MessageBox(NULL
, this->GetLastErrorMsg(), _T("TortoiseGit"), MB_ICONERROR
);
71 git_error_clear(m_err
);
72 git_pool_destroy (m_pool
); // free the allocated memory
76 // set up the Git_SSH param
77 CString tgit_ssh
= CRegString(_T("Software\\TortoiseGit\\SSH"));
78 if (tgit_ssh
.IsEmpty())
79 tgit_ssh
= CPathUtils::GetAppDirectory() + _T("TortoisePlink.exe");
80 tgit_ssh
.Replace('\\', '/');
81 if (!tgit_ssh
.IsEmpty())
83 git_config_t
* cfg
= (git_config_t
*)apr_hash_get ((apr_hash_t
*)ctx
->config
, Git_CONFIG_CATEGORY_CONFIG
,
85 git_config_set(cfg
, Git_CONFIG_SECTION_TUNNELS
, "ssh", CUnicodeUtils::GetUTF8(tgit_ssh
));
88 git_error_clear(git_config_ensure(NULL
, m_pool
));
90 // set up the configuration
91 m_err
= git_config_get_config (&(ctx
->config
), g_pConfigDir
, m_pool
);
97 GitStatus::~GitStatus(void)
100 git_error_clear(m_err
);
101 git_pool_destroy (m_pool
); // free the allocated memory
105 void GitStatus::ClearPool()
108 git_pool_clear(m_pool
);
113 CString
GitStatus::GetLastErrorMsg() const
115 // return Git::GetErrorString(m_err);
119 stdstring
GitStatus::GetLastErrorMsg() const
128 git_error_t
* ErrPtr
= m_err
;
131 msg
= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
135 /* Is this a Subversion-specific error code? */
136 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
137 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
138 msg
= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
139 /* Otherwise, this must be an APR error code. */
142 git_error_t
*temp_err
= NULL
;
143 const char * err_string
= NULL
;
144 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
147 git_error_clear (temp_err
);
148 msg
= _T("Can't recode error string from APR");
152 msg
= CUnicodeUtils::StdGetUnicode(err_string
);
158 while (ErrPtr
->child
)
160 ErrPtr
= ErrPtr
->child
;
164 msg
+= CUnicodeUtils::StdGetUnicode(ErrPtr
->message
);
168 /* Is this a Subversion-specific error code? */
169 if ((ErrPtr
->apr_err
> APR_OS_START_USEERR
)
170 && (ErrPtr
->apr_err
<= APR_OS_START_CANONERR
))
171 msg
+= CUnicodeUtils::StdGetUnicode(git_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)));
172 /* Otherwise, this must be an APR error code. */
175 git_error_t
*temp_err
= NULL
;
176 const char * err_string
= NULL
;
177 temp_err
= git_utf_cstring_to_utf8(&err_string
, apr_strerror (ErrPtr
->apr_err
, errbuf
, sizeof (errbuf
)-1), ErrPtr
->pool
);
180 git_error_clear (temp_err
);
181 msg
+= _T("Can't recode error string from APR");
185 msg
+= CUnicodeUtils::StdGetUnicode(err_string
);
192 } // if (m_err != NULL)
199 git_wc_status_kind
GitStatus::GetAllStatus(const CTGitPath
& path
, git_depth_t depth
)
201 git_wc_status_kind statuskind
;
202 // git_client_ctx_t * ctx;
204 // apr_pool_t * pool;
205 // git_error_t * err;
208 CString sProjectRoot
;
210 isDir
= path
.IsDirectory();
211 if (!path
.HasAdminDir(&sProjectRoot
))
212 return git_wc_status_none
;
214 // pool = git_pool_create (NULL); // create the memory pool
216 // git_error_clear(git_client_create_context(&ctx, pool));
218 // git_revnum_t youngest = Git_INVALID_REVNUM;
219 // git_opt_revision_t rev;
220 // rev.kind = git_opt_revision_unspecified;
221 statuskind
= git_wc_status_none
;
223 const BOOL bIsRecursive
= (depth
== git_depth_infinity
|| depth
== git_depth_unknown
); // taken from SVN source
226 CString s
= path
.GetWinPathString();
227 if (s
.GetLength() > sProjectRoot
.GetLength())
229 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == _T(':'))
230 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
232 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
235 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
236 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
240 err
= GetDirStatus(sProjectRoot
,sSubPath
,&statuskind
, isfull
,bIsRecursive
,isfull
,NULL
, NULL
);
245 err
= GetFileStatus(sProjectRoot
,sSubPath
,&statuskind
,isfull
, false,isfull
, NULL
,NULL
);
252 git_wc_status_kind
GitStatus::GetAllStatusRecursive(const CTGitPath
& path
)
254 return GetAllStatus(path
, git_depth_infinity
);
258 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
260 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
264 // static private method
265 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
269 case git_wc_status_none
:
271 case git_wc_status_unversioned
:
273 case git_wc_status_ignored
:
275 case git_wc_status_incomplete
:
277 case git_wc_status_normal
:
278 case git_wc_status_external
:
280 case git_wc_status_added
:
282 case git_wc_status_missing
:
284 case git_wc_status_deleted
:
286 case git_wc_status_replaced
:
288 case git_wc_status_modified
:
290 case git_wc_status_merged
:
292 case git_wc_status_conflicted
:
294 case git_wc_status_obstructed
:
300 git_revnum_t
GitStatus::GetStatus(const CTGitPath
& path
, bool update
/* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
302 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
303 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
304 // after the call again
306 // apr_hash_t * statushash;
307 // apr_hash_t * exthash;
308 // apr_array_header_t * statusarray;
309 // const sort_item* item;
311 // git_error_clear(m_err);
312 // statushash = apr_hash_make(m_pool);
313 // exthash = apr_hash_make(m_pool);
314 git_revnum_t youngest
= GIT_INVALID_REVNUM
;
315 // git_opt_revision_t rev;
316 // rev.kind = git_opt_revision_unspecified;
318 CString sProjectRoot
;
319 if ( !path
.HasAdminDir(&sProjectRoot
) )
322 struct hashbaton_t hashbaton
;
323 // hashbaton.hash = statushash;
324 // hashbaton.exthash = exthash;
325 hashbaton
.pThis
= this;
327 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
328 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
331 LPCTSTR lpszSubPath
= NULL
;
333 CString s
= path
.GetWinPathString();
334 if (s
.GetLength() > sProjectRoot
.GetLength())
336 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
337 lpszSubPath
= sSubPath
;
338 // skip initial slash if necessary
339 if (*lpszSubPath
== _T('\\'))
343 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
345 if(path
.IsDirectory())
347 m_err
= GetDirStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
, isfull
, false,!noignore
, NULL
, NULL
);
352 m_err
= GetFileStatus(sProjectRoot
,CString(lpszSubPath
),&m_status
.text_status
,isfull
, false,!noignore
, NULL
,NULL
);
356 // Error present if function is not under version control
357 if (m_err
) /*|| (apr_hash_count(statushash) == 0)*/
360 return GIT_INVALID_REVNUM
;
363 // Convert the unordered hash to an ordered, sorted array
364 /*statusarray = sort_hash (statushash,
365 sort_compare_items_as_paths,
368 // only the first entry is needed (no recurse)
369 // item = &APR_ARRAY_IDX (statusarray, 0, const sort_item);
371 // status = (git_wc_status2_t *) item->value;
376 // done to match TSVN functionality of this function (not sure if any code uses the reutrn val)
377 // if TGit does not need this, then change the return type of function
378 youngest
= g_Git
.GetHash(_T("HEAD"));
384 git_wc_status2_t
* GitStatus::GetFirstFileStatus(const CTGitPath
& /*path*/, CTGitPath
& /*retPath*/, bool /*update*/, git_depth_t
/*depth*/, bool /*bNoIgnore*/ /* = true */, bool /*bNoExternals*/ /* = false */)
386 static git_wc_status2 st
;
390 m_fileCache.Init( CStringA( path.GetWinPathString().GetString() ) );
391 MessageBox(NULL, path.GetWinPathString(), _T("GetFirstFile"), MB_OK);
392 m_fileCache.m_pFileIter = m_fileCache.m_pFiles;
393 st.text_status = git_wc_status_none;
395 if (m_fileCache.m_pFileIter)
397 switch(m_fileCache.m_pFileIter->nStatus)
399 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
400 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
401 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
404 //retPath.SetFromGit((const char*)item->key);
406 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
412 const sort_item
* item
;
414 git_error_clear(m_err
);
415 m_statushash
= apr_hash_make(m_pool
);
416 m_externalhash
= apr_hash_make(m_pool
);
417 headrev
= Git_INVALID_REVNUM
;
418 git_opt_revision_t rev
;
419 rev
.kind
= git_opt_revision_unspecified
;
420 struct hashbaton_t hashbaton
;
421 hashbaton
.hash
= m_statushash
;
422 hashbaton
.exthash
= m_externalhash
;
423 hashbaton
.pThis
= this;
424 m_statushashindex
= 0;
425 m_err
= git_client_status4 (&headrev
,
426 path
.GetGitApiPath(m_pool
),
433 bNoIgnore
, //noignore
434 bNoExternals
, //noexternals
440 // Error present if function is not under version control
441 if ((m_err
!= NULL
) || (apr_hash_count(m_statushash
) == 0))
446 // Convert the unordered hash to an ordered, sorted array
447 m_statusarray
= sort_hash (m_statushash
,
448 sort_compare_items_as_paths
,
451 // only the first entry is needed (no recurse)
452 m_statushashindex
= 0;
453 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
454 retPath
.SetFromGit((const char*)item
->key
);
455 return (git_wc_status2_t
*) item
->value
;
461 unsigned int GitStatus::GetVersionedCount() const
463 // return /**/m_fileCache.GetFileCount();
465 unsigned int count
= 0;
467 const sort_item
* item
;
468 for (unsigned int i
=0; i
<apr_hash_count(m_statushash
); ++i
)
470 item
= &APR_ARRAY_IDX(m_statusarray
, i
, const sort_item
);
473 if (GitStatus::GetMoreImportant(((git_wc_status_t
*)item
->value
)->text_status
, git_wc_status_ignored
)!=git_wc_status_ignored
)
481 git_wc_status2_t
* GitStatus::GetNextFileStatus(CTGitPath
& /*retPath*/)
483 static git_wc_status2 st
;
485 st
.text_status
= git_wc_status_none
;
487 /*if (m_fileCache.m_pFileIter)
489 switch(m_fileCache.m_pFileIter->nStatus)
491 case WGFS_Normal: st.text_status = git_wc_status_normal; break;
492 case WGFS_Modified: st.text_status = git_wc_status_modified; break;
493 case WGFS_Deleted: st.text_status = git_wc_status_deleted; break;
496 m_fileCache.m_pFileIter = m_fileCache.m_pFileIter->pNext;
502 const sort_item
* item
;
504 if ((m_statushashindex
+1) >= apr_hash_count(m_statushash
))
508 item
= &APR_ARRAY_IDX (m_statusarray
, m_statushashindex
, const sort_item
);
509 retPath
.SetFromGit((const char*)item
->key
);
510 return (git_wc_status2_t
*) item
->value
;
515 bool GitStatus::IsExternal(const CTGitPath
& /*path*/) const
518 if (apr_hash_get(m_externalhash
, path
.GetGitApiPath(m_pool
), APR_HASH_KEY_STRING
))
524 bool GitStatus::IsInExternal(const CTGitPath
& /*path*/) const
527 if (apr_hash_count(m_statushash
) == 0)
530 GitPool
localpool(m_pool
);
531 apr_hash_index_t
*hi
;
533 for (hi
= apr_hash_first(localpool
, m_externalhash
); hi
; hi
= apr_hash_next(hi
))
535 apr_hash_this(hi
, (const void**)&key
, NULL
, NULL
);
538 if (CTGitPath(CUnicodeUtils::GetUnicode(key
)).IsAncestorOf(path
))
547 void GitStatus::GetStatusString(git_wc_status_kind status
, size_t buflen
, TCHAR
* string
)
552 case git_wc_status_none
:
555 case git_wc_status_unversioned
:
556 buf
= _T("unversioned\0");
558 case git_wc_status_normal
:
559 buf
= _T("normal\0");
561 case git_wc_status_added
:
564 case git_wc_status_missing
:
565 buf
= _T("missing\0");
567 case git_wc_status_deleted
:
568 buf
= _T("deleted\0");
570 case git_wc_status_replaced
:
571 buf
= _T("replaced\0");
573 case git_wc_status_modified
:
574 buf
= _T("modified\0");
576 case git_wc_status_merged
:
577 buf
= _T("merged\0");
579 case git_wc_status_conflicted
:
580 buf
= _T("conflicted\0");
582 case git_wc_status_obstructed
:
583 buf
= _T("obstructed\0");
585 case git_wc_status_ignored
:
588 case git_wc_status_external
:
589 buf
= _T("external");
591 case git_wc_status_incomplete
:
592 buf
= _T("incomplete\0");
598 _stprintf_s(string
, buflen
, _T("%s"), buf
);
601 void GitStatus::GetStatusString(HINSTANCE hInst
, git_wc_status_kind status
, TCHAR
* string
, int size
, WORD lang
)
605 case git_wc_status_none
:
606 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
608 case git_wc_status_unversioned
:
609 LoadStringEx(hInst
, IDS_STATUSUNVERSIONED
, string
, size
, lang
);
611 case git_wc_status_normal
:
612 LoadStringEx(hInst
, IDS_STATUSNORMAL
, string
, size
, lang
);
614 case git_wc_status_added
:
615 LoadStringEx(hInst
, IDS_STATUSADDED
, string
, size
, lang
);
617 case git_wc_status_missing
:
618 LoadStringEx(hInst
, IDS_STATUSABSENT
, string
, size
, lang
);
620 case git_wc_status_deleted
:
621 LoadStringEx(hInst
, IDS_STATUSDELETED
, string
, size
, lang
);
623 case git_wc_status_replaced
:
624 LoadStringEx(hInst
, IDS_STATUSREPLACED
, string
, size
, lang
);
626 case git_wc_status_modified
:
627 LoadStringEx(hInst
, IDS_STATUSMODIFIED
, string
, size
, lang
);
629 case git_wc_status_merged
:
630 LoadStringEx(hInst
, IDS_STATUSMERGED
, string
, size
, lang
);
632 case git_wc_status_conflicted
:
633 LoadStringEx(hInst
, IDS_STATUSCONFLICTED
, string
, size
, lang
);
635 case git_wc_status_ignored
:
636 LoadStringEx(hInst
, IDS_STATUSIGNORED
, string
, size
, lang
);
638 case git_wc_status_obstructed
:
639 LoadStringEx(hInst
, IDS_STATUSOBSTRUCTED
, string
, size
, lang
);
641 case git_wc_status_external
:
642 LoadStringEx(hInst
, IDS_STATUSEXTERNAL
, string
, size
, lang
);
644 case git_wc_status_incomplete
:
645 LoadStringEx(hInst
, IDS_STATUSINCOMPLETE
, string
, size
, lang
);
648 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
654 CString
GitStatus::GetDepthString(git_depth_t depth
)
660 case git_depth_unknown
:
661 sDepth
.LoadString(IDS_Git_DEPTH_UNKNOWN
);
663 case git_depth_empty
:
664 sDepth
.LoadString(IDS_Git_DEPTH_EMPTY
);
666 case git_depth_files
:
667 sDepth
.LoadString(IDS_Git_DEPTH_FILES
);
669 case git_depth_immediates
:
670 sDepth
.LoadString(IDS_Git_DEPTH_IMMEDIATE
);
672 case git_depth_infinity
:
673 sDepth
.LoadString(IDS_Git_DEPTH_INFINITE
);
682 void GitStatus::GetDepthString(HINSTANCE
/*hInst*/, git_depth_t
/*depth*/, TCHAR
* /*string*/, int /*size*/, WORD
/*lang*/)
687 case git_depth_unknown
:
688 LoadStringEx(hInst
, IDS_SVN_DEPTH_UNKNOWN
, string
, size
, lang
);
690 case git_depth_empty
:
691 LoadStringEx(hInst
, IDS_SVN_DEPTH_EMPTY
, string
, size
, lang
);
693 case git_depth_files
:
694 LoadStringEx(hInst
, IDS_SVN_DEPTH_FILES
, string
, size
, lang
);
696 case git_depth_immediates
:
697 LoadStringEx(hInst
, IDS_SVN_DEPTH_IMMEDIATE
, string
, size
, lang
);
699 case git_depth_infinity
:
700 LoadStringEx(hInst
, IDS_SVN_DEPTH_INFINITE
, string
, size
, lang
);
707 int GitStatus::LoadStringEx(HINSTANCE hInstance
, UINT uID
, LPTSTR lpBuffer
, int nBufferMax
, WORD wLanguage
)
709 const STRINGRESOURCEIMAGE
* pImage
;
710 const STRINGRESOURCEIMAGE
* pImageEnd
;
716 HRSRC hResource
= FindResourceEx(hInstance
, RT_STRING
, MAKEINTRESOURCE(((uID
>>4)+1)), wLanguage
);
719 // try the default language before giving up!
720 hResource
= FindResource(hInstance
, MAKEINTRESOURCE(((uID
>>4)+1)), RT_STRING
);
724 hGlobal
= LoadResource(hInstance
, hResource
);
727 pImage
= (const STRINGRESOURCEIMAGE
*)::LockResource(hGlobal
);
731 nResourceSize
= ::SizeofResource(hInstance
, hResource
);
732 pImageEnd
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+nResourceSize
);
735 while ((iIndex
> 0) && (pImage
< pImageEnd
))
737 pImage
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+(sizeof(STRINGRESOURCEIMAGE
)+(pImage
->nLength
*sizeof(WCHAR
))));
740 if (pImage
>= pImageEnd
)
742 if (pImage
->nLength
== 0)
745 ret
= pImage
->nLength
;
746 if (pImage
->nLength
> nBufferMax
)
748 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
-1);
749 lpBuffer
[nBufferMax
-1] = 0;
753 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
);
759 BOOL
GitStatus::getallstatus(const struct wgFile_s
*pFile
, void *pUserData
)
761 git_wc_status_kind
* s
= (git_wc_status_kind
*)pUserData
;
762 *s
= GitStatus::GetMoreImportant(*s
, GitStatusFromWingit(pFile
->nStatus
));
766 BOOL
GitStatus::getstatus(const struct wgFile_s
*pFile
, void *pUserData
)
768 git_wc_status2_t
* s
= (git_wc_status2_t
*)pUserData
;
769 s
->prop_status
= s
->text_status
= GitStatus::GetMoreImportant(s
->prop_status
, GitStatusFromWingit(pFile
->nStatus
));
774 git_error_t
* GitStatus::getallstatus(void * baton
, const char * /*path*/, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
776 git_wc_status_kind
* s
= (git_wc_status_kind
*)baton
;
777 *s
= GitStatus::GetMoreImportant(*s
, status
->text_status
);
778 *s
= GitStatus::GetMoreImportant(*s
, status
->prop_status
);
784 git_error_t
* GitStatus::getstatushash(void * baton
, const char * path
, git_wc_status2_t
* status
, apr_pool_t
* /*pool*/)
786 hashbaton_t
* hash
= (hashbaton_t
*)baton
;
787 const StdStrAVector
& filterList
= hash
->pThis
->m_filterFileList
;
788 if (status
->text_status
== git_wc_status_external
)
790 apr_hash_set (hash
->exthash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, (const void*)1);
793 if(filterList
.size() > 0)
795 // We have a filter active - we're only interested in files which are in
797 if(!binary_search(filterList
.begin(), filterList
.end(), path
))
799 // This item is not in the filter - don't store it
803 git_wc_status2_t
* statuscopy
= git_wc_dup_status2 (status
, hash
->pThis
->m_pool
);
804 apr_hash_set (hash
->hash
, apr_pstrdup(hash
->pThis
->m_pool
, path
), APR_HASH_KEY_STRING
, statuscopy
);
808 apr_array_header_t
* GitStatus::sort_hash (apr_hash_t
*ht
,
809 int (*comparison_func
) (const GitStatus::sort_item
*, const GitStatus::sort_item
*),
812 apr_hash_index_t
*hi
;
813 apr_array_header_t
*ary
;
815 /* allocate an array with only one element to begin with. */
816 ary
= apr_array_make (pool
, 1, sizeof(sort_item
));
818 /* loop over hash table and push all keys into the array */
819 for (hi
= apr_hash_first (pool
, ht
); hi
; hi
= apr_hash_next (hi
))
821 sort_item
*item
= (sort_item
*)apr_array_push (ary
);
823 apr_hash_this (hi
, &item
->key
, &item
->klen
, &item
->value
);
826 /* now quick sort the array. */
827 qsort (ary
->elts
, ary
->nelts
, ary
->elt_size
,
828 (int (*)(const void *, const void *))comparison_func
);
833 int GitStatus::sort_compare_items_as_paths (const sort_item
*a
, const sort_item
*b
)
835 const char *astr
, *bstr
;
837 astr
= (const char*)a
->key
;
838 bstr
= (const char*)b
->key
;
839 return git_path_compare_paths (astr
, bstr
);
843 tgit_error_t
* GitStatus::cancel(void * /*baton*/)
846 volatile bool * canceled
= (bool *)baton
;
850 temp
.LoadString(IDS_Git_USERCANCELLED
);
851 return git_error_create(Git_ERR_CANCELLED
, NULL
, CUnicodeUtils::GetUTF8(temp
));
860 // Set-up a filter to restrict the files which will have their status stored by a get-status
861 void GitStatus::SetFilter(const CTGitPathList
& fileList
)
863 m_filterFileList
.clear();
864 for(int fileIndex
= 0; fileIndex
< fileList
.GetCount(); fileIndex
++)
866 // m_filterFileList.push_back(fileList[fileIndex].GetGitApiPath(m_pool));
868 // Sort the list so that we can do binary searches
869 std::sort(m_filterFileList
.begin(), m_filterFileList
.end());
872 void GitStatus::ClearFilter()
874 m_filterFileList
.clear();
879 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
881 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
)
885 CString path
= pathParam
;
887 TCHAR oldpath
[MAX_PATH
+1];
888 memset(oldpath
,0,MAX_PATH
+1);
890 path
.Replace(_T('\\'),_T('/'));
892 CString lowcasepath
=path
;
893 lowcasepath
.MakeLower();
897 git_wc_status_kind st
= git_wc_status_none
;
900 g_IndexFileMap
.GetFileStatus(gitdir
,path
,&st
,IsFull
,false,callback
,pData
,&hash
);
902 if( st
== git_wc_status_conflicted
)
906 callback(gitdir
+_T("/")+path
,st
,false,pData
);
910 if( st
== git_wc_status_unversioned
)
914 *status
= git_wc_status_unversioned
;
916 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
920 if( g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
922 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
924 if( g_IgnoreList
.IsIgnore(path
, gitdir
) )
926 st
= git_wc_status_ignored
;
930 callback(gitdir
+_T("/")+path
,st
, false, pData
);
935 if( st
== git_wc_status_normal
&& IsFull
)
938 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
941 SHARED_TREE_PTR treeptr
;
943 treeptr
=g_HeadFileMap
.SafeGet(gitdir
);
945 b
= treeptr
->m_Head
!= treeptr
->m_TreeHash
;
949 treeptr
->ReadHeadHash(gitdir
);
952 if( treeptr
->m_HeadFile
.IsEmpty() )
954 *status
=st
=git_wc_status_added
;
956 callback(gitdir
+_T("/")+path
,st
,false,pData
);
959 if(treeptr
->ReadTree())
961 treeptr
->m_LastModifyTimeHead
= 0;
962 //Check if init repository
963 *status
= treeptr
->m_Head
.IsEmpty()? git_wc_status_added
: st
;
965 callback(gitdir
+_T("/")+path
,*status
,false,pData
);
968 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
971 // Check Head Tree Hash;
975 int start
=SearchInSortVector(*treeptr
,lowcasepath
.GetBuffer(),-1);
976 lowcasepath
.ReleaseBuffer();
980 *status
=st
=git_wc_status_added
;
981 ATLTRACE(_T("File miss in head tree %s"), path
);
983 callback(gitdir
+_T("/")+path
,st
,false, pData
);
987 //staged and not commit
988 if( treeptr
->at(start
).m_Hash
!= hash
)
990 *status
=st
=git_wc_status_modified
;
992 callback(gitdir
+_T("/")+path
,st
, false, pData
);
1000 callback(gitdir
+_T("/")+path
,st
,false, pData
);
1007 *status
= git_wc_status_none
;
1015 int GitStatus::GetHeadHash(const CString
&gitdir
, CGitHash
&hash
)
1017 return g_HeadFileMap
.GetHeadHash(gitdir
, hash
);
1020 bool GitStatus::IsGitReposChanged(const CString
&gitdir
,const CString
&subpaths
, int mode
)
1022 if( mode
& GIT_MODE_INDEX
)
1024 return g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1027 if( mode
& GIT_MODE_HEAD
)
1029 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1033 if( mode
& GIT_MODE_IGNORE
)
1035 if(g_IgnoreList
.CheckIgnoreChanged(gitdir
,subpaths
))
1041 int GitStatus::LoadIgnoreFile(const CString
&gitdir
,const CString
&subpaths
)
1043 return g_IgnoreList
.LoadAllIgnoreFile(gitdir
,subpaths
);
1045 int GitStatus::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
)
1047 return g_IndexFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
1050 __int64
GitStatus::GetIndexFileTime(const CString
&gitdir
)
1052 SHARED_INDEX_PTR ptr
=g_IndexFileMap
.SafeGet(gitdir
);
1053 if(ptr
.get() == NULL
)
1056 return ptr
->m_LastModifyTime
;
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
));
1105 std::sort(list
.begin(), list
.end(), SortFileName
);
1109 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
)
1113 TCHAR oldpath
[MAX_PATH
+1];
1114 memset(oldpath
,0,MAX_PATH
+1);
1116 CString path
=subpath
;
1118 path
.Replace(_T('\\'),_T('/'));
1120 if(path
[path
.GetLength()-1] != _T('/'))
1121 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1123 CString lowcasepath
= path
;
1124 lowcasepath
.MakeLower();
1126 std::vector
<CGitFileName
> filelist
;
1127 GetFileList(gitdir
, subpath
, filelist
);
1131 g_IndexFileMap
.CheckAndUpdate(gitdir
,true);
1133 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
) || g_HeadFileMap
.IsHashChanged(gitdir
))
1135 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1136 treeptr
->ReadHeadHash(gitdir
);
1137 if(!treeptr
->ReadTree())
1139 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1143 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1144 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1146 if( indexptr
.get() == NULL
)
1149 std::vector
<CGitFileName
>::iterator it
;
1152 for(it
= filelist
.begin(); it
<filelist
.end();it
++)
1154 casepath
=onepath
= path
;
1155 onepath
.MakeLower();
1156 onepath
+= it
->m_FileName
;
1157 casepath
+= it
->m_CaseFileName
;
1159 LPTSTR onepathBuffer
= onepath
.GetBuffer();
1160 int pos
= SearchInSortVector(*indexptr
, onepathBuffer
, onepath
.GetLength());
1161 int posintree
= SearchInSortVector(*treeptr
, onepathBuffer
, onepath
.GetLength());
1162 onepath
.ReleaseBuffer();
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_modified
;
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 */
1238 LPTSTR lowcasepathBuffer
= lowcasepath
.GetBuffer();
1240 int pos
=SearchInSortVector(*indexptr
, lowcasepathBuffer
, lowcasepath
.GetLength());
1242 if(pos
>=0 && GetRangeInSortVector(*indexptr
, lowcasepathBuffer
, lowcasepath
.GetLength(), &start
, &end
, pos
))
1244 CGitIndexList::iterator it
;
1247 for(it
= indexptr
->begin()+start
; it
<= indexptr
->begin()+end
; it
++)
1249 int start
= lowcasepath
.GetLength();
1250 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1252 index
= (*it
).m_FileName
.GetLength();
1254 CString filename
= (*it
).m_FileName
.Mid(start
, index
-start
);
1255 if(oldstring
!= filename
)
1257 oldstring
= filename
;
1258 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1260 *status
= git_wc_status_deleted
;
1262 callback(gitdir
+_T("/")+filename
, *status
, false,pData
);
1269 pos
=SearchInSortVector(*treeptr
, lowcasepathBuffer
, lowcasepath
.GetLength());
1270 if(pos
>=0 && GetRangeInSortVector(*treeptr
, lowcasepathBuffer
, lowcasepath
.GetLength(), &start
, &end
, pos
) == 0)
1272 CGitHeadFileList::iterator it
;
1275 for(it
= treeptr
->begin()+start
; it
<= treeptr
->begin()+end
; it
++)
1277 int start
= lowcasepath
.GetLength();
1278 int index
= (*it
).m_FileName
.Find(_T('/'), start
);
1280 index
= (*it
).m_FileName
.GetLength();
1282 CString filename
= (*it
).m_FileName
.Mid(start
, index
-start
);
1283 if(oldstring
!= filename
)
1285 oldstring
= filename
;
1286 if(SearchInSortVector(filelist
, filename
.GetBuffer(), filename
.GetLength())<0)
1288 *status
= git_wc_status_modified
;
1290 callback(gitdir
+_T("/")+(*it
).m_FileName
, *status
, false,pData
);
1295 lowcasepath
.ReleaseBuffer();
1297 }/*End of if status*/
1305 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
)
1309 TCHAR oldpath
[MAX_PATH
+1];
1310 memset(oldpath
,0,MAX_PATH
+1);
1312 CString path
=subpath
;
1314 path
.Replace(_T('\\'),_T('/'));
1316 if(path
[path
.GetLength()-1] != _T('/'))
1317 path
+= _T('/'); //Add trail / to show it is directory, not file name.
1319 CString lowcasepath
= path
;
1320 lowcasepath
.MakeLower();
1324 g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
1326 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
1328 if (indexptr
== NULL
)
1330 *status
= git_wc_status_unversioned
;
1334 if(subpath
.IsEmpty() && (!indexptr
.use_count()))
1335 { // for new init repository
1336 *status
= git_wc_status_normal
;
1338 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1342 int pos
=SearchInSortVector(*indexptr
,lowcasepath
.GetBuffer(),lowcasepath
.GetLength());
1343 lowcasepath
.ReleaseBuffer();
1345 //Not In Version Contorl
1350 *status
= git_wc_status_unversioned
;
1352 callback(gitdir
+_T("/")+path
, *status
, false,pData
);
1355 //Check ignore always.
1357 if(::g_IgnoreList
.CheckIgnoreChanged(gitdir
,path
))
1358 g_IgnoreList
.LoadAllIgnoreFile(gitdir
,path
);
1360 if(g_IgnoreList
.IsIgnore(path
,gitdir
))
1361 *status
= git_wc_status_ignored
;
1363 *status
= git_wc_status_unversioned
;
1365 g_HeadFileMap
.CheckHeadUpdate(gitdir
);
1367 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1368 //Check init repository
1369 if(treeptr
->m_Head
.IsEmpty() && path
.IsEmpty())
1370 *status
= git_wc_status_normal
;
1374 else // In version control
1376 *status
= git_wc_status_normal
;
1383 end
=indexptr
->size()-1;
1385 LPTSTR lowcasepathBuffer
= lowcasepath
.GetBuffer();
1386 GetRangeInSortVector(*indexptr
, lowcasepathBuffer
, lowcasepath
.GetLength(), &start
, &end
, pos
);
1387 lowcasepath
.ReleaseBuffer();
1388 CGitIndexList::iterator it
;
1390 it
= indexptr
->begin()+start
;
1393 for(int i
=start
;i
<=end
;i
++)
1395 if( ((*it
).m_Flags
& CE_STAGEMASK
) !=0)
1397 *status
= git_wc_status_conflicted
;
1400 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1401 if(dirpos
<0 || IsRecursive
)
1402 callback(gitdir
+_T("\\")+ it
->m_FileName
,git_wc_status_conflicted
,false,pData
);
1410 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
1412 *status
= git_wc_status_normal
;
1414 if(g_HeadFileMap
.CheckHeadUpdate(gitdir
))
1416 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1418 treeptr
->ReadHeadHash(gitdir
);
1420 if(treeptr
->ReadTree())
1422 g_HeadFileMap
.SafeSet(gitdir
, treeptr
);
1426 it
= indexptr
->begin()+start
;
1430 //Check if new init repository
1431 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
1433 if( treeptr
->size() > 0 || treeptr
->m_Head
.IsEmpty() )
1435 for(int i
=start
;i
<=end
;i
++)
1437 pos
=SearchInSortVector(*treeptr
, (*it
).m_FileName
.GetBuffer(), -1);
1438 (*it
).m_FileName
.ReleaseBuffer();
1442 *status
= max(git_wc_status_modified
, *status
); // added file found
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_added
,false, pData
);
1454 if( pos
>=0 && treeptr
->at(pos
).m_Hash
!= (*it
).m_IndexHash
)
1456 *status
= max(git_wc_status_modified
, *status
); // modified file found
1459 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1460 if(dirpos
<0 || IsRecursive
)
1461 callback(gitdir
+_T("\\")+ it
->m_FileName
, git_wc_status_modified
,false, pData
);
1472 if( *status
== git_wc_status_normal
)
1474 pos
= SearchInSortVector(*treeptr
, lowcasepathBuffer
, lowcasepath
.GetLength());
1477 *status
= max(git_wc_status_modified
, *status
); // added file found
1483 GetRangeInSortVector(*treeptr
, lowcasepathBuffer
, lowcasepath
.GetLength(), &hstart
, &hend
, pos
);
1484 CGitHeadFileList::iterator hit
;
1485 hit
= treeptr
->begin() + hstart
;
1486 CGitHeadFileList::iterator lastElement
= treeptr
->end();
1487 for(int i
=hstart
; i
<= hend
&& hit
!= lastElement
; i
++)
1489 if( SearchInSortVector(*indexptr
,(*hit
).m_FileName
.GetBuffer(),-1) < 0)
1491 (*hit
).m_FileName
.ReleaseBuffer();
1492 *status
= max(git_wc_status_modified
, *status
); // deleted file found
1495 (*hit
).m_FileName
.ReleaseBuffer();
1503 lowcasepath
.ReleaseBuffer();
1504 // If define callback, it need update each file status.
1505 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
1506 // because git_wc_status_conflicted is highest.s
1507 if(callback
|| (*status
!= git_wc_status_conflicted
))
1512 CString sub
, currentPath
;
1513 it
= indexptr
->begin()+start
;
1514 for(int i
=start
;i
<=end
;i
++,it
++)
1518 //skip child directory
1519 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
1523 currentPath
= (*it
).m_FileName
.Left(pos
);
1524 if( callback
&& (sub
!= currentPath
) )
1527 ATLTRACE(_T("index subdir %s\n"),sub
);
1528 if(callback
) callback(gitdir
+ _T("\\")+sub
,
1529 git_wc_status_normal
,true, pData
);
1535 git_wc_status_kind filestatus
= git_wc_status_none
;
1537 GetFileStatus(gitdir
,(*it
).m_FileName
, &filestatus
,IsFul
, IsRecursive
,IsIgnore
, callback
,pData
);
1539 if (filestatus
> git_wc_status_normal
&& filestatus
!= git_wc_status_conflicted
)
1540 *status
= git_wc_status_modified
; // folders can only be modified or conflicted
1546 if(callback
) callback(gitdir
+_T("/")+subpath
,*status
,true, pData
);
1552 *status
= git_wc_status_none
;
1559 bool GitStatus::IsExistIndexLockFile(const CString
&gitdir
)
1561 CString sDirName
= gitdir
;
1565 if(PathFileExists(sDirName
+ _T("\\.git")))
1567 if(PathFileExists(g_AdminDirMap
.GetAdminDir(gitdir
) + _T("index.lock")))
1573 int x
= sDirName
.ReverseFind(_T('\\'));
1577 sDirName
= sDirName
.Left(x
);