Fixed issue #2241: Cleanup: result window of dry run should offer to "really remove"
[TortoiseGit.git] / src / TortoiseProc / Commands / CleanupCommand.cpp
blob0240f7b7833663478d9300ead325860a378d3d03
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.
20 #include "stdafx.h"
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)
32 CStringA t;
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'));
38 t += c;
39 i += 3;
41 else
43 t += s[i];
47 return CUnicodeUtils::GetUnicode(t);
50 struct SubmodulePayload
52 STRING_VECTOR &list;
53 CString basePath;
54 STRING_VECTOR prefixList;
55 SubmodulePayload(STRING_VECTOR &alist, CString abasePath = _T(""), STRING_VECTOR aprefixList = STRING_VECTOR())
56 : list(alist)
57 , basePath(abasePath)
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);
77 else
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);
92 return 0;
95 static bool GetSubmodulePathList(SubmodulePayload &payload)
97 CAutoRepository repo;
98 if (repo.Open(payload.basePath))
100 // Silence the warning message, submodule may not be initialized yet.
101 return false;
104 if (git_submodule_foreach(repo, SubmoduleCallback, &payload))
106 MessageBox(nullptr, CGit::GetLibGit2LastErr(_T("Could not get submodule list.")), _T("TortoiseGit"), MB_ICONERROR);
107 return false;
110 return true;
113 bool CleanupCommand::Execute()
115 bool bRet = false;
117 CCleanTypeDlg dlg;
118 if( dlg.DoModal() == IDOK)
120 bool quotepath = g_Git.GetConfigValueBool(_T("core.quotepath"));
122 while (true)
124 CString cmd;
125 cmd.Format(_T("git.exe clean"));
126 if (dlg.m_bDryRun || !dlg.m_bNoRecycleBin)
127 cmd += _T(" -n ");
128 if (dlg.m_bDir)
129 cmd += _T(" -d ");
130 switch (dlg.m_CleanType)
132 case 0:
133 cmd += _T(" -fx");
134 break;
135 case 1:
136 cmd += _T(" -f");
137 break;
138 case 2:
139 cmd += _T(" -fX");
140 break;
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)
152 CString path;
153 if (pathList[i].IsDirectory())
154 payload.prefixList.push_back(pathList[i].GetGitPathString());
155 else
156 payload.prefixList.push_back(pathList[i].GetContainingDirectory().GetGitPathString());
159 if (!GetSubmodulePathList(payload))
160 return FALSE;
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)
169 CString path;
170 if (this->pathList[i].IsDirectory())
171 path = pathList[i].GetGitPathString();
172 else
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;
191 if (!dlg.m_bDryRun)
192 idRetry = progress.m_PostFailCmdList.Add(CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY)));
193 else
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)));
200 else
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();
208 if (result == IDOK)
209 return TRUE;
210 if (progress.m_GitStatus && result == IDC_PROGRESS_BUTTON1 + idRetry)
211 continue;
212 if (!progress.m_GitStatus && result == IDC_PROGRESS_BUTTON1 + idDeleteRecycle)
214 dlg.m_bDryRun = FALSE;
215 dlg.m_bNoRecycleBin = FALSE;
216 continue;
218 if (!progress.m_GitStatus && result == IDC_PROGRESS_BUTTON1 + idDeleteNoRecycle)
220 dlg.m_bDryRun = FALSE;
221 dlg.m_bNoRecycleBin = TRUE;
222 continue;
224 break;
226 else
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)
239 CGit git;
240 CGit *pGit;
241 if (i == 0)
242 pGit = &g_Git;
243 else
245 git.m_CurrentDir = submoduleList[i - 1];
246 pGit = &git;
248 CString cmdout, cmdouterr;
249 if (pGit->Run(cmd, &cmdout, &cmdouterr, CP_UTF8))
251 MessageBox(nullptr, cmdouterr, _T("TortoiseGit"), MB_ICONERROR);
252 return FALSE;
255 if (sysProgressDlg.HasUserCancelled())
257 CMessageBox::Show(nullptr, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_OK);
258 return FALSE;
261 int pos = 0;
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();
268 if (quotepath)
270 tempPath = UnescapeQuotePath(tempPath.Trim(_T('"')));
272 if (i == 0)
273 delList.AddPath(CTGitPath(tempPath));
274 else
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);
284 return FALSE;
288 delList.DeleteAllFiles(true, false);
290 sysProgressDlg.Stop();
291 break;
295 #if 0
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)
307 SVN svn;
308 if (!svn.CleanUp(pathList[i]))
310 strFailedPaths += _T("- ") + pathList[i].GetWinPathString() + _T("\n");
311 strFailedPaths += svn.GetLastErrorMessage() + _T("\n\n");
313 else
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());
321 CString sPath;
322 bool bDir = false;
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());
342 progress.Stop();
344 CString strMessage;
345 if ( !strSuccessfullPaths.IsEmpty() )
347 CString tmp;
348 tmp.Format(IDS_PROC_CLEANUPFINISHED, (LPCTSTR)strSuccessfullPaths);
349 strMessage += tmp;
350 bRet = true;
352 if ( !strFailedPaths.IsEmpty() )
354 if (!strMessage.IsEmpty())
355 strMessage += _T("\n");
356 CString tmp;
357 tmp.Format(IDS_PROC_CLEANUPFINISHED_FAILED, (LPCTSTR)strFailedPaths);
358 strMessage += tmp;
359 bRet = false;
361 CMessageBox::Show(hwndExplorer, strMessage, _T("TortoiseGit"), MB_OK | (strFailedPaths.IsEmpty()?MB_ICONINFORMATION:MB_ICONERROR));
362 #endif
363 CShellUpdater::Instance().Flush();
364 return bRet;