1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009, 2011-2021, 2023 - 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 struct SubmodulePayload
34 STRING_VECTOR prefixList
;
35 SubmodulePayload(STRING_VECTOR
& alist
, CString abasePath
= L
"", const STRING_VECTOR
& aprefixList
= STRING_VECTOR())
38 , prefixList(aprefixList
)
43 static bool GetSubmodulePathList(SubmodulePayload
&payload
);
45 static int SubmoduleCallback(git_submodule
*sm
, const char * /*name*/, void *payload
)
47 auto spayload
= reinterpret_cast<SubmodulePayload
*>(payload
);
48 CString path
= CUnicodeUtils::GetUnicode(git_submodule_path(sm
));
49 CString
fullPath(spayload
->basePath
);
52 if (!PathIsDirectory(fullPath
))
54 if (spayload
->prefixList
.empty())
56 CTGitPath
subPath(spayload
->basePath
);
57 subPath
.AppendPathString(path
);
58 spayload
->list
.push_back(subPath
.GetGitPathString());
59 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
60 GetSubmodulePathList(tpayload
);
64 for (size_t i
= 0; i
< spayload
->prefixList
.size(); ++i
)
66 CString prefix
= spayload
->prefixList
.at(i
) + L
'/';
67 if (CStringUtils::StartsWith(path
, prefix
))
69 CTGitPath
subPath(spayload
->basePath
);
70 subPath
.AppendPathString(path
);
71 spayload
->list
.push_back(subPath
.GetGitPathString());
72 SubmodulePayload
tpayload(spayload
->list
, subPath
.GetGitPathString());
73 GetSubmodulePathList(tpayload
);
80 static bool GetSubmodulePathList(SubmodulePayload
&payload
)
82 CAutoRepository
repo(payload
.basePath
);
85 // Silence the warning message, submodule may not be initialized yet.
89 if (git_submodule_foreach(repo
, SubmoduleCallback
, &payload
))
91 MessageBox(GetExplorerHWND(), CGit::GetLibGit2LastErr(L
"Could not get submodule list."), L
"TortoiseGit", MB_ICONERROR
);
98 static bool GetFilesToCleanUp(CTGitPathList
& delList
, const CString
& baseCmd
, CGit
*pGit
, const CString
& path
, const boolean quotepath
, CSysProgressDlg
& sysProgressDlg
)
100 CString
cmd(baseCmd
);
102 cmd
+= L
" -- \"" + path
+ L
'"';
104 CString cmdout
, cmdouterr
;
105 if (pGit
->Run(cmd
, &cmdout
, &cmdouterr
, CP_UTF8
))
107 if (cmdouterr
.IsEmpty())
108 cmdouterr
.Format(IDS_GITEXEERROR_NOMESSAGE
, static_cast<LPCWSTR
>(cmdout
));
109 MessageBox(GetExplorerHWND(), cmdouterr
, L
"TortoiseGit", MB_ICONERROR
);
113 if (sysProgressDlg
.HasUserCancelled())
115 CMessageBox::Show(GetExplorerHWND(), IDS_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
120 CString token
= cmdout
.Tokenize(L
"\n", pos
);
121 while (!token
.IsEmpty())
123 if (CStringUtils::StartsWith(token
, L
"Would remove "))
125 CString tempPath
= token
.Mid(static_cast<int>(wcslen(L
"Would remove "))).TrimRight();
127 tempPath
= CStringUtils::UnescapeGitQuotePath(tempPath
.Trim(L
'"'));
128 delList
.AddPath(pGit
->CombinePath(tempPath
));
131 token
= cmdout
.Tokenize(L
"\n", pos
);
134 if (sysProgressDlg
.HasUserCancelled())
136 CMessageBox::Show(GetExplorerHWND(), IDS_USERCANCELLED
, IDS_APPNAME
, MB_OK
);
143 static bool DoCleanUp(const CTGitPathList
& pathList
, int cleanType
, bool bDir
, bool bDirUnmanagedRepos
, bool bSubmodules
, bool bDryRun
, bool bNoRecycleBin
)
146 cmd
.Format(L
"git.exe clean");
147 if (bDryRun
|| !bNoRecycleBin
)
163 if (bDirUnmanagedRepos
)
166 STRING_VECTOR submoduleList
;
169 SubmodulePayload
payload(submoduleList
);
170 payload
.basePath
= CTGitPath(g_Git
.m_CurrentDir
).GetGitPathString();
171 if (pathList
.GetCount() != 1 || pathList
.GetCount() == 1 && !pathList
[0].IsEmpty())
173 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
176 if (pathList
[i
].IsDirectory())
177 payload
.prefixList
.push_back(pathList
[i
].GetGitPathString());
179 payload
.prefixList
.push_back(pathList
[i
].GetContainingDirectory().GetGitPathString());
182 if (!GetSubmodulePathList(payload
))
184 std::sort(submoduleList
.begin(), submoduleList
.end());
187 if (bDryRun
|| bNoRecycleBin
)
189 CProgressDlg progress
;
190 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
193 if (pathList
[i
].IsDirectory() && !pathList
[i
].IsWCRoot())
194 path
= pathList
[i
].GetGitPathString();
196 path
= pathList
[i
].GetContainingDirectory().GetGitPathString();
198 if (pathList
[i
].IsWCRoot() && pathList
[i
].GetWinPathString() != g_Git
.m_CurrentDir
)
200 if (PathIsRelative(pathList
[i
].GetWinPathString()))
201 progress
.m_GitDirList
.push_back(g_Git
.CombinePath(pathList
[i
].GetWinPathString()));
203 progress
.m_GitDirList
.push_back(pathList
[i
].GetWinPathString());
206 progress
.m_GitDirList
.push_back(g_Git
.m_CurrentDir
);
207 progress
.m_GitCmdList
.push_back(cmd
+ (path
.IsEmpty() ? CString() : (L
" -- \"" + path
+ L
'"')));
210 for (CString dir
: submoduleList
)
212 progress
.m_GitDirList
.push_back(CTGitPath(dir
).GetWinPathString());
213 progress
.m_GitCmdList
.push_back(cmd
);
216 progress
.m_PostCmdCallback
= [&](DWORD status
, PostCmdList
& postCmdList
)
219 postCmdList
.emplace_back(IDS_MSGBOX_RETRY
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bDirUnmanagedRepos
, bSubmodules
, bDryRun
, bNoRecycleBin
); });
221 if (status
|| !bDryRun
)
226 postCmdList
.emplace_back(IDS_CLEAN_NO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bDirUnmanagedRepos
, bSubmodules
, FALSE
, TRUE
); });
227 postCmdList
.emplace_back(IDS_CLEAN_TO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bDirUnmanagedRepos
, bSubmodules
, FALSE
, FALSE
); });
231 postCmdList
.emplace_back(IDS_CLEAN_TO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bDirUnmanagedRepos
, bSubmodules
, FALSE
, FALSE
); });
232 postCmdList
.emplace_back(IDS_CLEAN_NO_RECYCLEBIN
, [&]{ DoCleanUp(pathList
, cleanType
, bDir
, bDirUnmanagedRepos
, bSubmodules
, FALSE
, TRUE
); });
236 INT_PTR result
= progress
.DoModal();
237 return result
== IDOK
;
241 CSysProgressDlg sysProgressDlg
;
242 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
243 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROC_CLEANUP_INFO1
)));
244 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
245 sysProgressDlg
.SetShowProgressBar(false);
246 sysProgressDlg
.ShowModeless(static_cast<HWND
>(nullptr), true);
248 bool quotepath
= g_Git
.GetConfigValueBool(L
"core.quotepath", true);
250 CTGitPathList delList
;
251 for (int i
= 0; i
< pathList
.GetCount(); ++i
)
254 if (pathList
[i
].IsDirectory() && !pathList
[i
].IsWCRoot())
255 path
= pathList
[i
].GetGitPathString();
257 path
= pathList
[i
].GetContainingDirectory().GetGitPathString();
259 if (pathList
[i
].IsWCRoot() && pathList
[i
].GetWinPathString() != g_Git
.m_CurrentDir
)
262 if (PathIsRelative(pathList
[i
].GetWinPathString()))
263 git
.m_CurrentDir
= g_Git
.CombinePath(pathList
[i
].GetWinPathString());
265 git
.m_CurrentDir
= pathList
[i
].GetWinPathString();
266 if (!GetFilesToCleanUp(delList
, cmd
, &git
, path
, quotepath
, sysProgressDlg
))
269 else if (!GetFilesToCleanUp(delList
, cmd
, &g_Git
, path
, quotepath
, sysProgressDlg
))
273 for (CString dir
: submoduleList
)
276 git
.m_CurrentDir
= dir
;
277 if (!GetFilesToCleanUp(delList
, cmd
, &git
, L
"", quotepath
, sysProgressDlg
))
281 delList
.DeleteAllFiles(true, false, true);
283 sysProgressDlg
.Stop();
289 bool CleanupCommand::Execute()
291 if (!GitAdminDir::HasAdminDir(g_Git
.m_CurrentDir
))
293 CMessageBox::Show(GetExplorerHWND(), IDS_NOWORKINGCOPY
, IDS_APPNAME
, MB_ICONERROR
);
300 dlg
.m_pathList
= pathList
;
301 if (dlg
.DoModal() == IDOK
)
303 bRet
= DoCleanUp(pathList
, dlg
.m_CleanType
, dlg
.m_bDir
== BST_CHECKED
, dlg
.m_bDirUnmanagedRepo
== BST_CHECKED
, dlg
.m_bSubmodules
== BST_CHECKED
, dlg
.m_bDryRun
== BST_CHECKED
, dlg
.m_bNoRecycleBin
== BST_CHECKED
);
305 CShellUpdater::Instance().Flush();