KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_git_mr_review.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
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 3
9 * of the License, or (at your option) any later version.
10 *
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.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
32#include <kiway.h>
33#include <reporter.h>
34
35#include <git2.h>
36#include <mutex>
37#include <wx/file.h>
38#include <wx/filename.h>
39#include <wx/log.h>
40#include <wx/msgdlg.h>
41#include <wx/stdpaths.h>
42#include <wx/time.h>
43#include <wx/utils.h>
44
45
47 const std::vector<wxString>& aRefList ) :
49 m_common( aCommon ),
50 m_repo( aCommon ? aCommon->GetRepo() : nullptr )
51{
52 m_listFiles->AppendColumn( _( "Path" ), wxLIST_FORMAT_LEFT, 400 );
53 m_listFiles->AppendColumn( _( "Status" ), wxLIST_FORMAT_LEFT, 100 );
54 m_listFiles->AppendColumn( _( "Old Path" ), wxLIST_FORMAT_LEFT, 200 );
55
56 for( const wxString& ref : aRefList )
57 {
58 m_comboBase->Append( ref );
59 m_comboHead->Append( ref );
60 }
61
62 if( !aRefList.empty() )
63 {
64 m_comboBase->SetSelection( 0 );
65 m_comboHead->SetSelection( aRefList.size() > 1 ? 1 : 0 );
66 }
67
68 SetupStandardButtons( { { wxID_CANCEL, _( "Close" ) } } );
70}
71
72
73void DIALOG_GIT_MR_REVIEW::OnClose( wxCloseEvent& aEvent )
74{
75 EndModal( wxID_CANCEL );
76}
77
78
79void DIALOG_GIT_MR_REVIEW::OnCompareClick( wxCommandEvent& aEvent )
80{
82}
83
84
85void DIALOG_GIT_MR_REVIEW::OnFileActivated( wxListEvent& aEvent )
86{
87 openFileDiff( aEvent.GetIndex() );
88}
89
90
92{
93 m_listFiles->DeleteAllItems();
94 m_changedFiles.clear();
95
96 if( !m_repo )
97 {
98 wxMessageBox( _( "No git repository available." ), _( "Compare Branches" ),
99 wxOK | wxICON_ERROR, this );
100 return;
101 }
102
103 wxString base = m_comboBase->GetValue();
104 wxString head = m_comboHead->GetValue();
105
106 if( base.IsEmpty() || head.IsEmpty() )
107 {
108 wxMessageBox( _( "Select base and head refs to compare." ),
109 _( "Compare Branches" ), wxOK | wxICON_INFORMATION, this );
110 return;
111 }
112
113 // Hold the lock while resolving and comparing refs.
114 std::unique_lock<std::mutex> lock = KIGIT::TryLockAction( *m_common, this );
115
116 if( !lock.owns_lock() )
117 return;
118
119 // Validate each ref resolves and peels to a tree; CompareRefs needs a
120 // tree on both sides.
121 auto validateRef =
122 [this]( const wxString& aRef ) -> bool
123 {
125
126 if( !treePtr )
127 {
128 wxMessageBox(
129 wxString::Format( _( "Could not resolve ref '%s' to a commit or tree." ),
130 aRef ),
131 _( "Compare Branches" ), wxOK | wxICON_ERROR, this );
132 return false;
133 }
134
135 return true;
136 };
137
138 if( !validateRef( base ) || !validateRef( head ) )
139 return;
140
142
143 lock.unlock();
144
145 if( m_changedFiles.empty() )
146 {
147 m_listFiles->InsertItem( 0, _( "No changes between selected refs." ) );
148 return;
149 }
150
151 long row = 0;
152
153 for( const KIGIT::CHANGED_FILE& f : m_changedFiles )
154 {
155 long idx = m_listFiles->InsertItem( row++, f.path );
156 m_listFiles->SetItem( idx, 1,
157 wxString::FromUTF8( KIGIT::FileChangeStatusToString( f.status ) ) );
158
159 if( !f.oldPath.IsEmpty() )
160 m_listFiles->SetItem( idx, 2, f.oldPath );
161 }
162}
163
164
165namespace
166{
167
171bool isPrettyFootprintChange( const wxString& aPath )
172{
174 return false;
175
176 wxFileName fn( aPath );
177 const wxArrayString& dirs = fn.GetDirs();
178
179 return !dirs.IsEmpty() && dirs.Last().EndsWith( wxS( ".pretty" ) );
180}
181
182
183} // namespace
184
185
187{
188 if( aListIndex < 0 || aListIndex >= static_cast<long>( m_changedFiles.size() ) )
189 return;
190
191 const KIGIT::CHANGED_FILE& file = m_changedFiles[aListIndex];
192
194 bool prettyFootprint = isPrettyFootprintChange( file.path );
195
196 if( prettyFootprint )
198
200 {
201 wxMessageBox( wxString::Format( _( "No diff handler for '%s'." ), file.path ),
202 _( "Compare File" ), wxOK | wxICON_INFORMATION, this );
203 return;
204 }
205
206 const wxString base = m_comboBase->GetValue();
207 const wxString head = m_comboHead->GetValue();
208
209 // The shared workspace owns a private scratch directory and cleans it up on scope exit. It
210 // checks out the whole tree for a schematic so its sub-sheets resolve, instead of a lone blob.
211 GIT_DIFF_WORKSPACE workspace;
212
213 if( !workspace.IsOk() )
214 {
215 wxMessageBox( _( "Could not create a temporary directory." ), _( "Compare File" ),
216 wxOK | wxICON_ERROR, this );
217 return;
218 }
219
220 // Extract both versions under the lock, then release it before opening the diff.
221 std::unique_lock<std::mutex> lock = KIGIT::TryLockAction( *m_common, this );
222
223 if( !lock.owns_lock() )
224 return;
225
226 const bool hasBase = file.status != KIGIT::FILE_CHANGE_STATUS::ADDED;
227 const bool hasHead = file.status != KIGIT::FILE_CHANGE_STATUS::REMOVED;
228 const wxString basePath = !file.oldPath.IsEmpty() ? file.oldPath : file.path;
229
230 wxString pathA;
231 wxString pathB;
232
233 if( prettyFootprint )
234 {
235 // FP_LIB_DIFFER needs a .pretty directory on each side. The footprint's name in the
236 // library is the filename without extension.
237 const wxString sep = wxFileName::GetPathSeparator();
238 const wxString fpFile = wxFileName( file.path ).GetFullName();
239
240 wxString dirA = workspace.Root() + sep + wxS( "base.pretty" );
241 wxString dirB = workspace.Root() + sep + wxS( "head.pretty" );
242
243 if( hasBase && !wxFileName::Mkdir( dirA, 0700, wxPATH_MKDIR_FULL ) )
244 return;
245
246 if( hasHead && !wxFileName::Mkdir( dirB, 0700, wxPATH_MKDIR_FULL ) )
247 return;
248
249 if( hasBase )
250 {
251 if( !KIGIT::PROJECT_GIT_UTILS::WriteBlobToFile( m_repo, base, basePath, dirA + sep + fpFile ) )
252 {
253 wxMessageBox( wxString::Format( _( "Could not extract %s from %s." ), basePath, base ),
254 _( "Compare File" ), wxOK | wxICON_ERROR, this );
255 return;
256 }
257
258 pathA = dirA;
259 }
260
261 if( hasHead )
262 {
263 if( !KIGIT::PROJECT_GIT_UTILS::WriteBlobToFile( m_repo, head, file.path, dirB + sep + fpFile ) )
264 {
265 wxMessageBox( wxString::Format( _( "Could not extract %s from %s." ), file.path, head ),
266 _( "Compare File" ), wxOK | wxICON_ERROR, this );
267 return;
268 }
269
270 pathB = dirB;
271 }
272 }
273 else
274 {
275 if( hasBase )
276 {
277 pathA = workspace.Materialize( m_repo, base, basePath, kind );
278
279 if( pathA.IsEmpty() )
280 {
281 wxMessageBox( wxString::Format( _( "Could not extract %s from %s." ), basePath, base ),
282 _( "Compare File" ), wxOK | wxICON_ERROR, this );
283 return;
284 }
285 }
286
287 if( hasHead )
288 {
289 pathB = workspace.Materialize( m_repo, head, file.path, kind );
290
291 if( pathB.IsEmpty() )
292 {
293 wxMessageBox( wxString::Format( _( "Could not extract %s from %s." ), file.path, head ),
294 _( "Compare File" ), wxOK | wxICON_ERROR, this );
295 return;
296 }
297 }
298 }
299
300 lock.unlock();
301
302 // pathA empty for ADDED, pathB empty for REMOVED. The kiface synthesizes an empty
303 // document for the missing side.
304 const wxString labelA = wxString::Format( wxS( "%s:%s" ), base, basePath );
305 const wxString labelB = wxString::Format( wxS( "%s:%s" ), head, file.path );
306
308 KICAD_DIFF::DispatchOpenDiffDialog( Kiway(), kind, pathA, pathB, labelA, labelB, this,
309 &reporter );
310
311 if( reporter.HasMessage() )
312 wxMessageBox( reporter.GetMessages(), _( "Compare File" ),
313 wxOK | wxICON_INFORMATION, this );
314}
DIALOG_GIT_MR_REVIEW_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Compare Branches"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(800, 550), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void OnFileActivated(wxListEvent &aEvent) override
void OnCompareClick(wxCommandEvent &aEvent) override
DIALOG_GIT_MR_REVIEW(wxWindow *aParent, KIGIT_COMMON *aCommon, const std::vector< wxString > &aRefList)
std::vector< KIGIT::CHANGED_FILE > m_changedFiles
void openFileDiff(long aListIndex)
void OnClose(wxCloseEvent &aEvent) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
A private scratch directory for extracting committed file versions to diff, removed when this object ...
bool IsOk() const
True when the scratch directory was created.
const wxString & Root() const
The scratch directory, for callers that lay out their own files (e.g. footprint libraries).
wxString Materialize(git_repository *aRepo, const wxString &aRef, const wxString &aRelPath, KICAD_DIFF::DOC_KIND aKind)
Extract aRelPath at aRef into a uniquely named slot and return the path to load, or an empty string w...
static bool WriteBlobToFile(git_repository *aRepo, const wxString &aRef, const wxString &aPath, const wxString &aDestPath)
Write a file as it exists at a git ref to a path on disk, so a committed version can be diffed.
The project's git session.
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
#define _(s)
DOC_KIND DocKindFromExtension(const wxString &aPath)
Map a path's extension to a DOC_KIND (.kicad_pcb -> PCB, .kicad_sch -> SCH, .kicad_sym -> SYM_LIB,...
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
int DispatchOpenDiffDialog(KIWAY &aKiway, DOC_KIND aKind, const wxString &aFileA, const wxString &aFileB, const wxString &aLabelA, const wxString &aLabelB, wxWindow *aParent, REPORTER *aReporter)
Open DIALOG_KICAD_DIFF on two files by calling the owning kiface's KIFACE_OPEN_DIFF_DIALOG function e...
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
std::vector< CHANGED_FILE > CompareRefs(git_repository *aRepo, const wxString &aBaseRef, const wxString &aHeadRef)
Compare two git refs (branch / tag / commit OID) within a repository and return the per-file change l...
git_tree * ResolveRefToTree(git_repository *aRepo, const wxString &aRef)
Resolve a string ref (branch name, short OID, full OID, tag) to its tree.
const char * FileChangeStatusToString(FILE_CHANGE_STATUS aStatus)
std::unique_lock< std::mutex > TryLockAction(KIGIT_COMMON &aCommon, wxWindow *aParent)
Try the action lock and show a busy message if it is unavailable.
FILE_CHANGE_STATUS status
IbisParser parser & reporter