1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009,2011-2014 - TortoiseGit
4 // Copyright (C) 2007-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "CleanupCommand.h"
23 #include "MessageBox.h"
24 #include "ProgressDlg.h"
25 #include "ShellUpdater.h"
26 #include "CleanTypeDlg.h"
27 #include "..\Utils\UnicodeUtils.h"
28 #include "SysProgressDlg.h"
30 static CString
UnescapeQuotePath(CString s
)
33 for (int i
= 0; i
< s
.GetLength(); ++i
)
35 if (s
[i
] == '\\' && i
+ 3 < s
.GetLength())
37 char c
= (char)((s
[i
+ 1] - '0') * 64 + (s
[i
+ 2] - '0') * 8 + (s
[i
+ 3] - '0'));
47 return CUnicodeUtils::GetUnicode(t
);
50 struct SubmodulePayload
54 STRING_VECTOR prefixList
;
55 SubmodulePayload(STRING_VECTOR
&alist
, CString abasePath
= _T(""), STRING_VECTOR aprefixList
= STRING_VECTOR())
58 , prefixList(aprefixList
)
63 static bool GetSubmodulePathList(SubmodulePayload
&payload
);
65 static int SubmoduleCallback(git_submodule
*sm
, const char * /*name*/, void *payload
)
67 auto spayload
= (SubmodulePayload
*)payload
;
68 CString path
= CUnicodeUtils::GetUnicode(git_submodule_path(sm
));
69 if (spayload
->prefixList
.empty())
71 CTGitPath
subPath(spayload
->basePath
);
72 subPath
.AppendPathString(path
);
73 spayload
->list
.push_back(subPath
.GetGitPathString());
74 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
75 GetSubmodulePathList(tpayload
);
79 for (size_t i
= 0; i
< spayload
->prefixList
.size(); ++i
)
81 CString prefix
= spayload
->prefixList
.at(i
) + _T("/");
82 if (path
.Left(prefix
.GetLength()) == prefix
)
84 CTGitPath
subPath(spayload
->basePath
);
85 subPath
.AppendPathString(path
);
86 spayload
->list
.push_back(subPath
.GetGitPathString());
87 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
88 GetSubmodulePathList(tpayload
);
95 static bool GetSubmodulePathList(SubmodulePayload
&payload
)
98 if (repo
.Open(payload
.basePath
))
100 // Silence the warning message, submodule may not be initialized yet.
104 if (git_submodule_foreach(repo
, SubmoduleCallback
, &payload
))
106 MessageBox(nullptr, CGit::GetLibGit2LastErr(_T("Could not get submodule list.")), _T("TortoiseGit"), MB_ICONERROR
);
113 bool CleanupCommand::Execute()
118 if( dlg
.DoModal() == IDOK
)
120 bool quotepath
= g_Git
.GetConfigValueBool(_T("core.quotepath"));
125 cmd
.Format(_T("git.exe clean"));
126 if (dlg
.m_bDryRun
|| !dlg
.m_bNoRecycleBin
)
130 switch (dlg
.m_CleanType
)
143 STRING_VECTOR submoduleList
;
144 SubmodulePayload
payload(submoduleList
);
145 if (dlg
.m_bSubmodules
)
147 payload
.basePath
= CTGitPath(g_Git
.m_CurrentDir
).GetGitPathString();
148 if (pathList
.GetCount() != 1 || pathList
.GetCount() == 1 && !pathList
[0].IsEmpty())
150 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
153 if (pathList
[i
].IsDirectory())
154 payload
.prefixList
.push_back(pathList
[i
].GetGitPathString());
156 payload
.prefixList
.push_back(pathList
[i
].GetContainingDirectory().GetGitPathString());
159 if (!GetSubmodulePathList(payload
))
161 std::sort(submoduleList
.begin(), submoduleList
.end());
164 if (dlg
.m_bDryRun
|| dlg
.m_bNoRecycleBin
)
166 CProgressDlg progress
;
167 for (int i
= 0; i
< this->pathList
.GetCount(); ++i
)
170 if (this->pathList
[i
].IsDirectory())
171 path
= pathList
[i
].GetGitPathString();
173 path
= pathList
[i
].GetContainingDirectory().GetGitPathString();
175 progress
.m_GitDirList
.push_back(g_Git
.m_CurrentDir
);
176 progress
.m_GitCmdList
.push_back(cmd
+ _T(" \"") + path
+ _T("\""));
179 if (dlg
.m_bSubmodules
)
181 for (CString dir
: submoduleList
)
183 progress
.m_GitDirList
.push_back(CTGitPath(dir
).GetWinPathString());
184 progress
.m_GitCmdList
.push_back(cmd
);
188 INT_PTR idRetry
= -1;
189 INT_PTR idDeleteRecycle
= -1;
190 INT_PTR idDeleteNoRecycle
= -1;
192 idRetry
= progress
.m_PostFailCmdList
.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY
)));
195 if (dlg
.m_bNoRecycleBin
)
197 idDeleteNoRecycle
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_CLEAN_NO_RECYCLEBIN
)));
198 idDeleteRecycle
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_CLEAN_TO_RECYCLEBIN
)));
202 idDeleteRecycle
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_CLEAN_TO_RECYCLEBIN
)));
203 idDeleteNoRecycle
= progress
.m_PostCmdList
.Add(CString(MAKEINTRESOURCE(IDS_CLEAN_NO_RECYCLEBIN
)));
207 INT_PTR result
= progress
.DoModal();
210 if (progress
.m_GitStatus
&& result
== IDC_PROGRESS_BUTTON1
+ idRetry
)
212 if (!progress
.m_GitStatus
&& result
== IDC_PROGRESS_BUTTON1
+ idDeleteRecycle
)
214 dlg
.m_bDryRun
= FALSE
;
215 dlg
.m_bNoRecycleBin
= FALSE
;
218 if (!progress
.m_GitStatus
&& result
== IDC_PROGRESS_BUTTON1
+ idDeleteNoRecycle
)
220 dlg
.m_bDryRun
= FALSE
;
221 dlg
.m_bNoRecycleBin
= TRUE
;
228 CSysProgressDlg sysProgressDlg
;
229 sysProgressDlg
.SetAnimation(IDR_CLEANUPANI
);
230 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
231 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CLEANUP_INFO1
)));
232 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
233 sysProgressDlg
.SetShowProgressBar(false);
234 sysProgressDlg
.ShowModeless((HWND
)NULL
, true);
236 CTGitPathList delList
;
237 for (size_t i
= 0; i
<= submoduleList
.size(); ++i
)
245 git
.m_CurrentDir
= submoduleList
[i
- 1];
248 CString cmdout
, cmdouterr
;
249 if (pGit
->Run(cmd
, &cmdout
, &cmdouterr
, CP_UTF8
))
251 MessageBox(nullptr, cmdouterr
, _T("TortoiseGit"), MB_ICONERROR
);
255 if (sysProgressDlg
.HasUserCancelled())
257 CMessageBox::Show(nullptr, IDS_SVN_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
262 CString token
= cmdout
.Tokenize(_T("\n"), pos
);
263 while (!token
.IsEmpty())
265 if (token
.Mid(0, 13) == _T("Would remove "))
267 CString tempPath
= token
.Mid(13).TrimRight();
270 tempPath
= UnescapeQuotePath(tempPath
.Trim(_T('"')));
273 delList
.AddPath(CTGitPath(tempPath
));
275 delList
.AddPath(CTGitPath(submoduleList
[i
- 1] + "/" + tempPath
));
278 token
= cmdout
.Tokenize(_T("\n"), pos
);
281 if (sysProgressDlg
.HasUserCancelled())
283 CMessageBox::Show(nullptr, IDS_SVN_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
288 delList
.DeleteAllFiles(true, false);
290 sysProgressDlg
.Stop();
296 CProgressDlg progress
;
297 progress
.SetTitle(IDS_PROC_CLEANUP
);
298 progress
.SetAnimation(IDR_CLEANUPANI
);
299 progress
.SetShowProgressBar(false);
300 progress
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CLEANUP_INFO1
)));
301 progress
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROC_CLEANUP_INFO2
)));
302 progress
.ShowModeless(hwndExplorer
);
304 CString strSuccessfullPaths
, strFailedPaths
;
305 for (int i
=0; i
<pathList
.GetCount(); ++i
)
308 if (!svn
.CleanUp(pathList
[i
]))
310 strFailedPaths
+= _T("- ") + pathList
[i
].GetWinPathString() + _T("\n");
311 strFailedPaths
+= svn
.GetLastErrorMessage() + _T("\n\n");
315 strSuccessfullPaths
+= _T("- ") + pathList
[i
].GetWinPathString() + _T("\n");
317 // after the cleanup has finished, crawl the path downwards and send a change
318 // notification for every directory to the shell. This will update the
319 // overlays in the left tree view of the explorer.
320 CDirFileEnum
crawler(pathList
[i
].GetWinPathString());
323 CTSVNPathList updateList
;
324 while (crawler
.NextFile(sPath
, &bDir
))
326 if ((bDir
) && (!g_SVNAdminDir
.IsAdminDirPath(sPath
)))
328 updateList
.AddPath(CTSVNPath(sPath
));
331 updateList
.AddPath(pathList
[i
]);
332 CShellUpdater::Instance().AddPathsForUpdate(updateList
);
333 CShellUpdater::Instance().Flush();
334 updateList
.SortByPathname(true);
335 for (INT_PTR i
=0; i
<updateList
.GetCount(); ++i
)
337 SHChangeNotify(SHCNE_UPDATEITEM
, SHCNF_PATH
, updateList
[i
].GetWinPath(), NULL
);
338 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": notify change for path %s\n"), updateList
[i
].GetWinPath());
345 if ( !strSuccessfullPaths
.IsEmpty() )
348 tmp
.Format(IDS_PROC_CLEANUPFINISHED
, (LPCTSTR
)strSuccessfullPaths
);
352 if ( !strFailedPaths
.IsEmpty() )
354 if (!strMessage
.IsEmpty())
355 strMessage
+= _T("\n");
357 tmp
.Format(IDS_PROC_CLEANUPFINISHED_FAILED
, (LPCTSTR
)strFailedPaths
);
361 CMessageBox::Show(hwndExplorer
, strMessage
, _T("TortoiseGit"), MB_OK
| (strFailedPaths
.IsEmpty()?MB_ICONINFORMATION
:MB_ICONERROR
));
363 CShellUpdater::Instance().Flush();