1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009, 2011-2016 - 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'));
45 return CUnicodeUtils::GetUnicode(t
);
48 struct SubmodulePayload
52 STRING_VECTOR prefixList
;
53 SubmodulePayload(STRING_VECTOR
&alist
, CString abasePath
= L
"", STRING_VECTOR aprefixList
= STRING_VECTOR())
56 , prefixList(aprefixList
)
61 static bool GetSubmodulePathList(SubmodulePayload
&payload
);
63 static int SubmoduleCallback(git_submodule
*sm
, const char * /*name*/, void *payload
)
65 auto spayload
= reinterpret_cast<SubmodulePayload
*>(payload
);
66 CString path
= CUnicodeUtils::GetUnicode(git_submodule_path(sm
));
67 CString
fullPath(spayload
->basePath
);
70 if (!PathIsDirectory(fullPath
))
72 if (spayload
->prefixList
.empty())
74 CTGitPath
subPath(spayload
->basePath
);
75 subPath
.AppendPathString(path
);
76 spayload
->list
.push_back(subPath
.GetGitPathString());
77 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
78 GetSubmodulePathList(tpayload
);
82 for (size_t i
= 0; i
< spayload
->prefixList
.size(); ++i
)
84 CString prefix
= spayload
->prefixList
.at(i
) + L
'/';
85 if (CStringUtils::StartsWith(path
, prefix
))
87 CTGitPath
subPath(spayload
->basePath
);
88 subPath
.AppendPathString(path
);
89 spayload
->list
.push_back(subPath
.GetGitPathString());
90 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
91 GetSubmodulePathList(tpayload
);
98 static bool GetSubmodulePathList(SubmodulePayload
&payload
)
100 CAutoRepository
repo(payload
.basePath
);
103 // Silence the warning message, submodule may not be initialized yet.
107 if (git_submodule_foreach(repo
, SubmoduleCallback
, &payload
))
109 MessageBox(hWndExplorer
, CGit::GetLibGit2LastErr(L
"Could not get submodule list."), L
"TortoiseGit", MB_ICONERROR
);
116 static bool GetFilesToCleanUp(CTGitPathList
& delList
, const CString
& baseCmd
, CGit
*pGit
, const CString
& path
, const boolean quotepath
, CSysProgressDlg
& sysProgressDlg
)
118 CString
cmd(baseCmd
);
120 cmd
+= L
" -- \"" + path
+ L
'"';
122 CString cmdout
, cmdouterr
;
123 if (pGit
->Run(cmd
, &cmdout
, &cmdouterr
, CP_UTF8
))
125 if (cmdouterr
.IsEmpty())
126 cmdouterr
.Format(IDS_GITEXEERROR_NOMESSAGE
, (LPCTSTR
)cmdout
);
127 MessageBox(hWndExplorer
, cmdouterr
, L
"TortoiseGit", MB_ICONERROR
);
131 if (sysProgressDlg
.HasUserCancelled())
133 CMessageBox::Show(hWndExplorer
, IDS_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
138 CString token
= cmdout
.Tokenize(L
"\n", pos
);
139 while (!token
.IsEmpty())
141 if (token
.Mid(0, 13) == L
"Would remove ")
143 CString tempPath
= token
.Mid(13).TrimRight();
145 tempPath
= UnescapeQuotePath(tempPath
.Trim(L
'"'));
146 delList
.AddPath(pGit
->CombinePath(tempPath
));
149 token
= cmdout
.Tokenize(L
"\n", pos
);
152 if (sysProgressDlg
.HasUserCancelled())
154 CMessageBox::Show(hWndExplorer
, IDS_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
161 static bool DoCleanUp(const CTGitPathList
& pathList
, int cleanType
, bool bDir
, bool bSubmodules
, bool bDryRun
, bool bNoRecycleBin
)
164 cmd
.Format(L
"git.exe clean");
165 if (bDryRun
|| !bNoRecycleBin
)
182 STRING_VECTOR submoduleList
;
185 SubmodulePayload
payload(submoduleList
);
186 payload
.basePath
= CTGitPath(g_Git
.m_CurrentDir
).GetGitPathString();
187 if (pathList
.GetCount() != 1 || pathList
.GetCount() == 1 && !pathList
[0].IsEmpty())
189 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
192 if (pathList
[i
].IsDirectory())
193 payload
.prefixList
.push_back(pathList
[i
].GetGitPathString());
195 payload
.prefixList
.push_back(pathList
[i
].GetContainingDirectory().GetGitPathString());
198 if (!GetSubmodulePathList(payload
))
200 std::sort(submoduleList
.begin(), submoduleList
.end());
203 if (bDryRun
|| bNoRecycleBin
)
205 CProgressDlg progress
;
206 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
209 if (pathList
[i
].IsDirectory())
210 path
= pathList
[i
].GetGitPathString();
212 path
= pathList
[i
].GetContainingDirectory().GetGitPathString();
214 progress
.m_GitDirList
.push_back(g_Git
.m_CurrentDir
);
215 progress
.m_GitCmdList
.push_back(cmd
+ L
" -- \"" + path
+ L
'"');
218 for (CString dir
: submoduleList
)
220 progress
.m_GitDirList
.push_back(CTGitPath(dir
).GetWinPathString());
221 progress
.m_GitCmdList
.push_back(cmd
);
224 progress
.m_PostCmdCallback
= [&](DWORD status
, PostCmdList
& postCmdList
)
227 postCmdList
.emplace_back(IDS_MSGBOX_RETRY
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bSubmodules
, bDryRun
, bNoRecycleBin
); });
229 if (status
|| !bDryRun
)
234 postCmdList
.emplace_back(IDS_CLEAN_NO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bSubmodules
, FALSE
, TRUE
); });
235 postCmdList
.emplace_back(IDS_CLEAN_TO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bSubmodules
, FALSE
, FALSE
); });
239 postCmdList
.emplace_back(IDS_CLEAN_TO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bSubmodules
, FALSE
, FALSE
); });
240 postCmdList
.emplace_back(IDS_CLEAN_NO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bSubmodules
, FALSE
, TRUE
); });
244 INT_PTR result
= progress
.DoModal();
245 return result
== IDOK
;
249 CSysProgressDlg sysProgressDlg
;
250 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
251 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CLEANUP_INFO1
)));
252 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
253 sysProgressDlg
.SetShowProgressBar(false);
254 sysProgressDlg
.ShowModeless((HWND
)nullptr, true);
256 bool quotepath
= g_Git
.GetConfigValueBool(L
"core.quotepath");
258 CTGitPathList delList
;
259 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
262 if (pathList
[i
].IsDirectory())
263 path
= pathList
[i
].GetGitPathString();
265 path
= pathList
[i
].GetContainingDirectory().GetGitPathString();
267 if (!GetFilesToCleanUp(delList
, cmd
, &g_Git
, path
, quotepath
, sysProgressDlg
))
271 for (CString dir
: submoduleList
)
274 git
.m_CurrentDir
= dir
;
275 if (!GetFilesToCleanUp(delList
, cmd
, &git
, L
"", quotepath
, sysProgressDlg
))
279 delList
.DeleteAllFiles(true, false, true);
281 sysProgressDlg
.Stop();
287 bool CleanupCommand::Execute()
292 dlg
.m_pathList
= pathList
;
293 if (dlg
.DoModal() == IDOK
)
295 bRet
= DoCleanUp(pathList
, dlg
.m_CleanType
, dlg
.m_bDir
== BST_CHECKED
, dlg
.m_bSubmodules
== BST_CHECKED
, dlg
.m_bDryRun
== BST_CHECKED
, dlg
.m_bNoRecycleBin
== BST_CHECKED
);
297 CShellUpdater::Instance().Flush();