KiCad PCB EDA Suite
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
project_tree_pane.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 (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5 * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <stack>
27#include <thread>
28#include <git2.h>
29
30#include <wx/regex.h>
31#include <wx/stdpaths.h>
32#include <wx/string.h>
33#include <wx/msgdlg.h>
34#include <wx/textdlg.h>
35#include <wx/timer.h>
36
37#include <advanced_config.h>
38#include <bitmaps.h>
39#include <bitmap_store.h>
40#include <confirm.h>
43#include <gestfich.h>
44#include <macros.h>
45#include <trace_helpers.h>
48#include <core/kicad_algo.h>
49#include <paths.h>
51#include <scoped_set_reset.h>
52#include <string_utils.h>
53#include <thread_pool.h>
54#include <launch_ext.h>
55#include <wx/dcclient.h>
56#include <wx/settings.h>
57
72
74
75#include "project_tree_item.h"
76#include "project_tree.h"
77#include "pgm_kicad.h"
78#include "kicad_id.h"
79#include "kicad_manager_frame.h"
80
81#include "project_tree_pane.h"
82#include <widgets/kistatusbar.h>
83
84#include <kiplatform/io.h>
85#include <kiplatform/secrets.h>
86
87
88/* Note about the project tree build process:
89 * Building the project tree can be *very* long if there are a lot of subdirectories in the
90 * working directory. Unfortunately, this happens easily if the project file *.pro is in the
91 * user's home directory.
92 * So the tree project is built "on demand":
93 * First the tree is built from the current directory and shows files and subdirs.
94 * > First level subdirs trees are built (i.e subdirs contents are not read)
95 * > When expanding a subdir, each subdir contains is read, and the corresponding sub tree is
96 * populated on the fly.
97 */
98
99// list of files extensions listed in the tree project window
100// Add extensions in a compatible regex format to see others files types
101static const wxChar* s_allowedExtensionsToList[] = {
102 wxT( "^.*\\.pro$" ),
103 wxT( "^.*\\.kicad_pro$" ),
104 wxT( "^.*\\.pdf$" ),
105 wxT( "^.*\\.sch$" ), // Legacy Eeschema files
106 wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
107 wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
108 wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
109 wxT( "^[^$].*\\.kicad_dru$" ), // Design rule files
110 wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad drawing sheet files
111 wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
112 wxT( "^.*\\.net$" ), // pcbnew netlist file
113 wxT( "^.*\\.cir$" ), // Spice netlist file
114 wxT( "^.*\\.lib$" ), // Legacy schematic library file
115 wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
116 wxT( "^.*\\.txt$" ), // Text files
117 wxT( "^.*\\.md$" ), // Markdown files
118 wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
119 wxT( "^.*\\.gbr$" ), // Gerber file
120 wxT( "^.*\\.gbrjob$" ), // Gerber job file
121 wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
122 wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
123 wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
124 wxT( "^.*\\.gm[0-9]{1,2}$" ), // Gerber mechanical layer file (deprecated Protel ext)
125 wxT( "^.*\\.gko$" ), // Gerber keepout layer file (deprecated Protel ext)
126 wxT( "^.*\\.odt$" ),
127 wxT( "^.*\\.htm$" ),
128 wxT( "^.*\\.html$" ),
129 wxT( "^.*\\.rpt$" ), // Report files
130 wxT( "^.*\\.csv$" ), // Report files in comma separated format
131 wxT( "^.*\\.pos$" ), // Footprint position files
132 wxT( "^.*\\.cmp$" ), // CvPcb cmp/footprint link files
133 wxT( "^.*\\.drl$" ), // Excellon drill files
134 wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
135 wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
136 wxT( "^.*\\.svg$" ), // SVG print/plot files
137 wxT( "^.*\\.ps$" ), // PostScript plot files
138 wxT( "^.*\\.zip$" ), // Zip archive files
139 wxT( "^.*\\.kicad_jobset" ), // KiCad jobs file
140 nullptr // end of list
141};
142
143
151BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
152 EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onSelect )
153 EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onExpand )
154 EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, PROJECT_TREE_PANE::onRight )
161
177
179
180 EVT_IDLE( PROJECT_TREE_PANE::onIdle )
181 EVT_PAINT( PROJECT_TREE_PANE::onPaint )
182END_EVENT_TABLE()
183
184
185wxDECLARE_EVENT( UPDATE_ICONS, wxCommandEvent );
186
188 wxSashLayoutWindow( parent, ID_LEFT_FRAME, wxDefaultPosition, wxDefaultSize,
189 wxNO_BORDER | wxTAB_TRAVERSAL )
190{
191 m_Parent = parent;
192 m_TreeProject = nullptr;
193 m_isRenaming = false;
194 m_selectedItem = nullptr;
195 m_watcherNeedReset = false;
196 m_gitLastError = GIT_ERROR_NONE;
197 m_watcher = nullptr;
198 m_gitIconsInitialized = false;
199
200 Bind( wxEVT_FSWATCHER,
201 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
202
203 Bind( wxEVT_SYS_COLOUR_CHANGED,
204 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
205
206 m_gitSyncTimer.SetOwner( this );
207 m_gitStatusTimer.SetOwner( this );
208 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this, m_gitSyncTimer.GetId() );
209 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this, m_gitStatusTimer.GetId() );
210 /*
211 * Filtering is now inverted: the filters are actually used to _enable_ support
212 * for a given file type.
213 */
214 for( int ii = 0; s_allowedExtensionsToList[ii] != nullptr; ii++ )
215 m_filters.emplace_back( s_allowedExtensionsToList[ii] );
216
217 m_filters.emplace_back( wxT( "^no KiCad files found" ) );
218
219 ReCreateTreePrj();
220}
221
222
224{
225 Unbind( wxEVT_FSWATCHER,
226 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
227 Unbind( wxEVT_SYS_COLOUR_CHANGED,
228 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
229
230 m_gitSyncTimer.Stop();
231 m_gitStatusTimer.Stop();
232 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this,
233 m_gitSyncTimer.GetId() );
234 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this,
235 m_gitStatusTimer.GetId() );
237}
238
239
241{
242 if( m_watcher )
243 {
244 m_watcher->RemoveAll();
245 m_watcher->SetOwner( nullptr );
246 delete m_watcher;
247 m_watcher = nullptr;
248 }
249}
250
251
253{
254 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
255
256 if( tree_data.size() != 1 )
257 return;
258
259 wxString prj_filename = tree_data[0]->GetFileName();
260
261 m_Parent->LoadProject( prj_filename );
262}
263
264
265void PROJECT_TREE_PANE::onOpenDirectory( wxCommandEvent& event )
266{
267 // Get the root directory name:
268 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
269
270 for( PROJECT_TREE_ITEM* item_data : tree_data )
271 {
272 // Ask for the new sub directory name
273 wxString curr_dir = item_data->GetDir();
274
275 if( curr_dir.IsEmpty() )
276 {
277 // Use project path if the tree view path was empty.
278 curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
279
280 // As a last resort use the user's documents folder.
281 if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
283
284 if( !curr_dir.IsEmpty() )
285 curr_dir += wxFileName::GetPathSeparator();
286 }
287
288 LaunchExternal( curr_dir );
289 }
290}
291
292
293void PROJECT_TREE_PANE::onCreateNewDirectory( wxCommandEvent& event )
294{
295 // Get the root directory name:
296 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
297
298 for( PROJECT_TREE_ITEM* item_data : tree_data )
299 {
300 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
301
302 // Ask for the new sub directory name
303 wxString curr_dir = item_data->GetDir();
304
305 if( curr_dir.IsEmpty() )
306 curr_dir = prj_dir;
307
308 wxString new_dir = wxGetTextFromUser( _( "Directory name:" ), _( "Create New Directory" ) );
309
310 if( new_dir.IsEmpty() )
311 return;
312
313 wxString full_dirname = curr_dir + wxFileName::GetPathSeparator() + new_dir;
314
315 if( !wxMkdir( full_dirname ) )
316 return;
317
318 addItemToProjectTree( full_dirname, item_data->GetId(), nullptr, false );
319 }
320}
321
322
324{
325 switch( type )
326 {
327 case TREE_FILE_TYPE::LEGACY_PROJECT: return FILEEXT::LegacyProjectFileExtension;
328 case TREE_FILE_TYPE::JSON_PROJECT: return FILEEXT::ProjectFileExtension;
329 case TREE_FILE_TYPE::LEGACY_SCHEMATIC: return FILEEXT::LegacySchematicFileExtension;
330 case TREE_FILE_TYPE::SEXPR_SCHEMATIC: return FILEEXT::KiCadSchematicFileExtension;
331 case TREE_FILE_TYPE::LEGACY_PCB: return FILEEXT::LegacyPcbFileExtension;
332 case TREE_FILE_TYPE::SEXPR_PCB: return FILEEXT::KiCadPcbFileExtension;
333 case TREE_FILE_TYPE::GERBER: return FILEEXT::GerberFileExtensionsRegex;
334 case TREE_FILE_TYPE::GERBER_JOB_FILE: return FILEEXT::GerberJobFileExtension;
335 case TREE_FILE_TYPE::HTML: return FILEEXT::HtmlFileExtension;
336 case TREE_FILE_TYPE::PDF: return FILEEXT::PdfFileExtension;
337 case TREE_FILE_TYPE::TXT: return FILEEXT::TextFileExtension;
338 case TREE_FILE_TYPE::MD: return FILEEXT::MarkdownFileExtension;
339 case TREE_FILE_TYPE::NET: return FILEEXT::NetlistFileExtension;
340 case TREE_FILE_TYPE::NET_SPICE: return FILEEXT::SpiceFileExtension;
341 case TREE_FILE_TYPE::CMP_LINK: return FILEEXT::FootprintAssignmentFileExtension;
342 case TREE_FILE_TYPE::REPORT: return FILEEXT::ReportFileExtension;
343 case TREE_FILE_TYPE::FP_PLACE: return FILEEXT::FootprintPlaceFileExtension;
344 case TREE_FILE_TYPE::DRILL: return FILEEXT::DrillFileExtension;
345 case TREE_FILE_TYPE::DRILL_NC: return "nc";
346 case TREE_FILE_TYPE::DRILL_XNC: return "xnc";
347 case TREE_FILE_TYPE::SVG: return FILEEXT::SVGFileExtension;
348 case TREE_FILE_TYPE::DRAWING_SHEET: return FILEEXT::DrawingSheetFileExtension;
349 case TREE_FILE_TYPE::FOOTPRINT_FILE: return FILEEXT::KiCadFootprintFileExtension;
350 case TREE_FILE_TYPE::SCHEMATIC_LIBFILE: return FILEEXT::LegacySymbolLibFileExtension;
351 case TREE_FILE_TYPE::SEXPR_SYMBOL_LIB_FILE: return FILEEXT::KiCadSymbolLibFileExtension;
352 case TREE_FILE_TYPE::DESIGN_RULES: return FILEEXT::DesignRulesFileExtension;
353 case TREE_FILE_TYPE::ZIP_ARCHIVE: return FILEEXT::ArchiveFileExtension;
354 case TREE_FILE_TYPE::JOBSET_FILE: return FILEEXT::KiCadJobSetFileExtension;
355
356 case TREE_FILE_TYPE::ROOT:
357 case TREE_FILE_TYPE::UNKNOWN:
358 case TREE_FILE_TYPE::MAX:
359 case TREE_FILE_TYPE::DIRECTORY: break;
360 }
361
362 return wxEmptyString;
363}
364
365
366std::vector<wxString> getProjects( const wxDir& dir )
367{
368 std::vector<wxString> projects;
369 wxString dir_filename;
370 bool haveFile = dir.GetFirst( &dir_filename );
371
372 while( haveFile )
373 {
374 wxFileName file( dir_filename );
375
376 if( file.GetExt() == FILEEXT::LegacyProjectFileExtension
377 || file.GetExt() == FILEEXT::ProjectFileExtension )
378 projects.push_back( file.GetName() );
379
380 haveFile = dir.GetNext( &dir_filename );
381 }
382
383 return projects;
384}
385
386
387static git_repository* get_git_repository_for_file( const char* filename )
388{
389 git_repository* repo = nullptr;
390 git_buf repo_path = GIT_BUF_INIT;
391
392 // Find the repository path for the given file
393 if( git_repository_discover( &repo_path, filename, 0, NULL ) != GIT_OK )
394 {
395 wxLogTrace( traceGit, "Can't repo discover %s: %s", filename, KIGIT_COMMON::GetLastGitError() );
396 return nullptr;
397 }
398
399 KIGIT::GitBufPtr repo_path_ptr( &repo_path );
400
401 if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK )
402 {
403 wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr, KIGIT_COMMON::GetLastGitError() );
404 return nullptr;
405 }
406
407 return repo;
408}
409
410
411wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
412 const wxTreeItemId& aParent,
413 std::vector<wxString>* aProjectNames,
414 bool aRecurse )
415{
416 TREE_FILE_TYPE type = TREE_FILE_TYPE::UNKNOWN;
417 wxFileName fn( aName );
418
419 if( KIPLATFORM::IO::IsFileHidden( aName ) )
420 return wxTreeItemId();
421
422 if( wxDirExists( aName ) )
423 {
424 type = TREE_FILE_TYPE::DIRECTORY;
425 }
426 else
427 {
428 // Filter
429 wxRegEx reg;
430 bool addFile = false;
431
432 for( const wxString& m_filter : m_filters )
433 {
434 wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
435 wxString::Format( "Regex %s failed to compile.", m_filter ) );
436
437 if( reg.Matches( aName ) )
438 {
439 addFile = true;
440 break;
441 }
442 }
443
444 if( !addFile )
445 return wxTreeItemId();
446
447 for( int i = static_cast<int>( TREE_FILE_TYPE::LEGACY_PROJECT );
448 i < static_cast<int>( TREE_FILE_TYPE::MAX ); i++ )
449 {
450 wxString ext = GetFileExt( (TREE_FILE_TYPE) i );
451
452 if( ext == wxT( "" ) )
453 continue;
454
455 if( reg.Compile( wxString::FromAscii( "^.*\\." ) + ext + wxString::FromAscii( "$" ),
456 wxRE_ICASE ) && reg.Matches( aName ) )
457 {
458 type = (TREE_FILE_TYPE) i;
459 break;
460 }
461 }
462 }
463
464 wxString file = wxFileNameFromPath( aName );
465 wxFileName currfile( file );
466 wxFileName project( m_Parent->GetProjectFileName() );
467
468 // Ignore legacy projects with the same name as the current project
469 if( ( type == TREE_FILE_TYPE::LEGACY_PROJECT )
470 && ( currfile.GetName().CmpNoCase( project.GetName() ) == 0 ) )
471 {
472 return wxTreeItemId();
473 }
474
475 if( currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::LEGACY_SCHEMATIC )
476 || currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::SEXPR_SCHEMATIC ) )
477 {
478 if( aProjectNames )
479 {
480 if( !alg::contains( *aProjectNames, currfile.GetName() ) )
481 return wxTreeItemId();
482 }
483 else
484 {
485 PROJECT_TREE_ITEM* parentTreeItem = GetItemIdData( aParent );
486 wxDir parentDir( parentTreeItem->GetDir() );
487 std::vector<wxString> projects = getProjects( parentDir );
488
489 if( !alg::contains( projects, currfile.GetName() ) )
490 return wxTreeItemId();
491 }
492 }
493
494 // also check to see if it is already there.
495 wxTreeItemIdValue cookie;
496 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
497
498 while( kid.IsOk() )
499 {
500 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
501
502 if( itemData && itemData->GetFileName() == aName )
503 return itemData->GetId(); // well, we would have added it, but it is already here!
504
505 kid = m_TreeProject->GetNextChild( aParent, cookie );
506 }
507
508 // Only show current files if both legacy and current files are present
509 if( type == TREE_FILE_TYPE::LEGACY_PROJECT || type == TREE_FILE_TYPE::JSON_PROJECT
510 || type == TREE_FILE_TYPE::LEGACY_SCHEMATIC || type == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
511 {
512 kid = m_TreeProject->GetFirstChild( aParent, cookie );
513
514 while( kid.IsOk() )
515 {
516 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
517
518 if( itemData )
519 {
520 wxFileName fname( itemData->GetFileName() );
521
522 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
523 {
524 switch( type )
525 {
526 case TREE_FILE_TYPE::LEGACY_PROJECT:
527 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
528 return wxTreeItemId();
529
530 break;
531
532 case TREE_FILE_TYPE::LEGACY_SCHEMATIC:
533 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
534 return wxTreeItemId();
535
536 break;
537
538 case TREE_FILE_TYPE::JSON_PROJECT:
539 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
540 m_TreeProject->Delete( kid );
541
542 break;
543
544 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
545 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
546 m_TreeProject->Delete( kid );
547
548 break;
549
550 default:
551 break;
552 }
553 }
554 }
555
556 kid = m_TreeProject->GetNextChild( aParent, cookie );
557 }
558 }
559
560 // Append the item (only appending the filename not the full path):
561 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
562 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
563
564 m_TreeProject->SetItemData( newItemId, data );
565 data->SetState( 0 );
566
567 // Mark root files (files which have the same aName as the project)
568 wxString fileName = currfile.GetName().Lower();
569 wxString projName = project.GetName().Lower();
570
571 if( fileName == projName || fileName.StartsWith( projName + "-" ) )
572 data->SetRootFile( true );
573
574#ifndef __WINDOWS__
575 bool subdir_populated = false;
576#endif
577
578 // This section adds dirs and files found in the subdirs
579 // in this case AddFile is recursive, but for the first level only.
580 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
581 {
582 wxDir dir( aName );
583
584 if( dir.IsOpened() ) // protected dirs will not open properly.
585 {
586 std::vector<wxString> projects = getProjects( dir );
587 wxString dir_filename;
588 bool haveFile = dir.GetFirst( &dir_filename );
589
590 data->SetPopulated( true );
591
592#ifndef __WINDOWS__
593 subdir_populated = aRecurse;
594#endif
595
596 while( haveFile )
597 {
598 // Add name in tree, but do not recurse
599 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
600 addItemToProjectTree( path, newItemId, &projects, false );
601
602 haveFile = dir.GetNext( &dir_filename );
603 }
604 }
605
606 // Sort filenames by alphabetic order
607 m_TreeProject->SortChildren( newItemId );
608 }
609
610#ifndef __WINDOWS__
611 if( subdir_populated )
612 m_watcherNeedReset = true;
613#endif
614
615 return newItemId;
616}
617
618
620{
621 std::lock_guard<std::mutex> lock1( m_gitStatusMutex );
622 std::lock_guard<std::mutex> lock2( m_gitTreeCacheMutex );
624
625 tp.wait_for_tasks();
626 m_gitStatusTimer.Stop();
627 m_gitSyncTimer.Stop();
628 m_gitTreeCache.clear();
629 m_gitStatusIcons.clear();
630
631 wxString pro_dir = m_Parent->GetProjectFileName();
632
633 if( !m_TreeProject )
634 m_TreeProject = new PROJECT_TREE( this );
635 else
636 m_TreeProject->DeleteAllItems();
637
638 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
639 return;
640
642 {
643 git_repository_free( m_TreeProject->GetGitRepo() );
644 m_TreeProject->SetGitRepo( nullptr );
645 m_gitIconsInitialized = false;
646 }
647
648 wxFileName fn = pro_dir;
649 bool prjReset = false;
650
651 if( !fn.IsOk() )
652 {
653 fn.Clear();
654 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
655 fn.SetName( NAMELESS_PROJECT );
657 prjReset = true;
658 }
659
660 bool prjOpened = fn.FileExists();
661
662 // Bind the git repository to the project tree (if it exists)
663 if( Pgm().GetCommonSettings()->m_Git.enableGit )
664 {
665 m_TreeProject->SetGitRepo( get_git_repository_for_file( fn.GetPath().c_str() ) );
666
668 {
669 m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
670 m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
672 }
673 }
674
675 // We may have opened a legacy project, in which case GetProjectFileName will return the
676 // name of the migrated (new format) file, which may not have been saved to disk yet.
677 if( !prjOpened && !prjReset )
678 {
680 prjOpened = fn.FileExists();
681
682 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
684 }
685
686 // root tree:
687 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
688 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
689 m_TreeProject->SetItemBold( m_root, true );
690
691 // The main project file is now a JSON file
692 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( TREE_FILE_TYPE::JSON_PROJECT,
693 fn.GetFullPath(), m_TreeProject );
694
695 m_TreeProject->SetItemData( m_root, data );
696
697 // Now adding all current files if available
698 if( prjOpened )
699 {
700 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
701 wxDir dir( pro_dir );
702
703 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
704 {
705 std::vector<wxString> projects = getProjects( dir );
706 wxString filename;
707 bool haveFile = dir.GetFirst( &filename );
708
709 while( haveFile )
710 {
711 if( filename != fn.GetFullName() )
712 {
713 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
714
715 // Add items living in the project directory, and populate the item
716 // if it is a directory (sub directories will be not populated)
717 addItemToProjectTree( name, m_root, &projects, true );
718 }
719
720 haveFile = dir.GetNext( &filename );
721 }
722 }
723 }
724 else
725 {
726 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
727 }
728
729 m_TreeProject->Expand( m_root );
730
731 // Sort filenames by alphabetic order
732 m_TreeProject->SortChildren( m_root );
733
734 CallAfter( [this] ()
735 {
736 wxLogTrace( traceGit, "PROJECT_TREE_PANE::ReCreateTreePrj: starting timers" );
737 m_gitSyncTimer.Start( 100, wxTIMER_ONE_SHOT );
738 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
739 } );
740}
741
742
744{
745 git_repository* repo = m_TreeProject->GetGitRepo();
746
747 if( !repo )
748 return false;
749
750 git_status_options opts;
751 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
752
753 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
754 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
755 | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
756
757 git_status_list* status_list = nullptr;
758
759 if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK )
760 {
761 wxLogError( _( "Failed to get status list: %s" ), KIGIT_COMMON::GetLastGitError() );
762 return false;
763 }
764
765 KIGIT::GitStatusListPtr status_list_ptr( status_list );
766 return ( git_status_list_entrycount( status_list ) > 0 );
767}
768
769
770void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
771{
772 wxTreeItemId curr_item = Event.GetItem();
773
774 // Ensure item is selected (Under Windows right click does not select the item)
775 m_TreeProject->SelectItem( curr_item );
776
777 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
779 wxFileName prj_dir( Prj().GetProjectPath(), wxEmptyString );
780 wxFileName git_dir( git->GetGitRootDirectory(), wxEmptyString );
781 prj_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
782 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
783 git_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
784 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
785 wxString prj_name = prj_dir.GetFullPath();
786 wxString git_name = git_dir.GetFullPath();
787
788 bool can_switch_to_project = true;
789 bool can_create_new_directory = true;
790 bool can_open_this_directory = true;
791 bool can_edit = true;
792 bool can_rename = true;
793 bool can_delete = true;
794 bool run_jobs = false;
795
796 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
797 bool vcs_can_commit = hasChangedFiles();
798 bool vcs_can_init = !vcs_has_repo;
799 bool vcs_can_remove = vcs_has_repo && git_name.StartsWith( prj_name ); // This means the .git is a subdirectory of the project
800 bool vcs_can_fetch = vcs_has_repo && git->HasPushAndPullRemote();
801 bool vcs_can_push = vcs_can_fetch && git->HasLocalCommits();
802 bool vcs_can_pull = vcs_can_fetch;
803 bool vcs_can_switch = vcs_has_repo;
804 bool vcs_menu = Pgm().GetCommonSettings()->m_Git.enableGit;
805
806 // Check if the libgit2 library has been successfully initialized
807#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
808 int major, minor, rev;
809 bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK );
810#else
811 //Work around libgit2 API change for supporting older platforms
812 bool libgit_init = true;
813#endif
814
815 vcs_menu &= libgit_init;
816
817 if( selection.size() == 0 )
818 return;
819
820 // Remove things that don't make sense for multiple selections
821 if( selection.size() != 1 )
822 {
823 can_switch_to_project = false;
824 can_create_new_directory = false;
825 can_rename = false;
826 }
827
828 for( PROJECT_TREE_ITEM* item : selection )
829 {
830 // Check for empty project
831 if( !item )
832 {
833 can_switch_to_project = false;
834 can_edit = false;
835 can_rename = false;
836 continue;
837 }
838
839 can_delete = item->CanDelete();
840 can_rename = item->CanRename();
841
842 switch( item->GetType() )
843 {
844 case TREE_FILE_TYPE::JSON_PROJECT:
845 case TREE_FILE_TYPE::LEGACY_PROJECT:
846 can_rename = false;
847
848 if( item->GetId() == m_TreeProject->GetRootItem() )
849 {
850 can_switch_to_project = false;
851 }
852 else
853 {
854 can_create_new_directory = false;
855 can_open_this_directory = false;
856 }
857 break;
858
859 case TREE_FILE_TYPE::DIRECTORY:
860 can_switch_to_project = false;
861 can_edit = false;
862 break;
863
864 case TREE_FILE_TYPE::ZIP_ARCHIVE:
865 case TREE_FILE_TYPE::PDF:
866 can_edit = false;
867 can_switch_to_project = false;
868 can_create_new_directory = false;
869 can_open_this_directory = false;
870 break;
871
872 case TREE_FILE_TYPE::JOBSET_FILE:
873 run_jobs = true;
874 can_edit = false;
876
877 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
878 case TREE_FILE_TYPE::SEXPR_PCB:
880
881 default:
882 can_switch_to_project = false;
883 can_create_new_directory = false;
884 can_open_this_directory = false;
885
886 break;
887 }
888 }
889
890 wxMenu popup_menu;
891 wxString text;
892 wxString help_text;
893
894 if( can_switch_to_project )
895 {
896 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
897 _( "Close all editors, and switch to the selected project" ),
898 KiBitmap( BITMAPS::open_project ) );
899 popup_menu.AppendSeparator();
900 }
901
902 if( can_create_new_directory )
903 {
904 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
905 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
906 }
907
908 if( can_open_this_directory )
909 {
910 if( selection.size() == 1 )
911 {
912#ifdef __APPLE__
913 text = _( "Reveal in Finder" );
914 help_text = _( "Reveals the directory in a Finder window" );
915#else
916 text = _( "Open Directory in File Explorer" );
917 help_text = _( "Opens the directory in the default system file manager" );
918#endif
919 }
920 else
921 {
922#ifdef __APPLE__
923 text = _( "Reveal in Finder" );
924 help_text = _( "Reveals the directories in a Finder window" );
925#else
926 text = _( "Open Directories in File Explorer" );
927 help_text = _( "Opens the directories in the default system file manager" );
928#endif
929 }
930
931 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
932 KiBitmap( BITMAPS::directory_browser ) );
933 }
934
935 if( can_edit )
936 {
937 if( selection.size() == 1 )
938 help_text = _( "Open the file in a Text Editor" );
939 else
940 help_text = _( "Open files in a Text Editor" );
941
942 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
943 KiBitmap( BITMAPS::editor ) );
944 }
945
946 if( run_jobs && selection.size() == 1 )
947 {
948 KIUI::AddMenuItem( &popup_menu, ID_JOBS_RUN, _( "Run Jobs" ), help_text,
949 KiBitmap( BITMAPS::exchange ) );
950 }
951
952 if( can_rename )
953 {
954 if( selection.size() == 1 )
955 {
956 text = _( "Rename File..." );
957 help_text = _( "Rename file" );
958 }
959 else
960 {
961 text = _( "Rename Files..." );
962 help_text = _( "Rename files" );
963 }
964
965 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
966 KiBitmap( BITMAPS::right ) );
967 }
968
969 if( can_delete )
970 {
971 if( selection.size() == 1 )
972 help_text = _( "Delete the file and its content" );
973 else
974 help_text = _( "Delete the files and their contents" );
975
976 if( can_switch_to_project
977 || can_create_new_directory
978 || can_open_this_directory
979 || can_edit
980 || can_rename )
981 {
982 popup_menu.AppendSeparator();
983 }
984
985#ifdef __WINDOWS__
986 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
987 KiBitmap( BITMAPS::trash ) );
988#else
989 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
990 KiBitmap( BITMAPS::trash ) );
991#endif
992 }
993
994 if( vcs_menu )
995 {
996 wxMenu* vcs_submenu = new wxMenu();
997 wxMenu* branch_submenu = new wxMenu();
998 wxMenuItem* vcs_menuitem = nullptr;
999
1000 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
1001 _( "Add Project to Version Control..." ),
1002 _( "Initialize a new repository" ) );
1003 vcs_menuitem->Enable( vcs_can_init );
1004
1005
1006 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
1007 _( "Commit changes to the local repository" ) );
1008 vcs_menuitem->Enable( vcs_can_commit );
1009
1010 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
1011 _( "Push committed local changes to remote repository" ) );
1012 vcs_menuitem->Enable( vcs_can_push );
1013
1014 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
1015 _( "Pull changes from remote repository into local" ) );
1016 vcs_menuitem->Enable( vcs_can_pull );
1017
1018 vcs_submenu->AppendSeparator();
1019
1020 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
1021 _( "Commit changes to the local repository" ) );
1022 vcs_menuitem->Enable( vcs_can_commit );
1023
1024 vcs_submenu->AppendSeparator();
1025
1026 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
1027 // _( "Show changes between the repository and working tree" ) );
1028 // vcs_menuitem->Enable( vcs_can_diff );
1029
1030 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
1031
1032 // Skip the first one (that is the current branch)
1033 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
1034 {
1035 wxString msg = _( "Switch to branch " ) + branchNames[ii];
1036 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
1037 vcs_menuitem->Enable( vcs_can_switch );
1038 }
1039
1040 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
1041 _( "Switch to a different branch" ) );
1042 vcs_menuitem->Enable( vcs_can_switch );
1043
1044 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
1045
1046 vcs_submenu->AppendSeparator();
1047
1048 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Remove Version Control" ),
1049 _( "Delete all version control files from the project directory." ) );
1050 vcs_menuitem->Enable( vcs_can_remove );
1051
1052 popup_menu.AppendSeparator();
1053 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
1054 }
1055
1056 if( popup_menu.GetMenuItemCount() > 0 )
1057 PopupMenu( &popup_menu );
1058}
1059
1060
1062{
1063 wxString editorname = Pgm().GetTextEditor();
1064
1065 if( editorname.IsEmpty() )
1066 {
1067 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
1068 return;
1069 }
1070
1071 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1072
1073 for( PROJECT_TREE_ITEM* item_data : tree_data )
1074 {
1075 wxString fullFileName = item_data->GetFileName();
1076
1077 if( !fullFileName.IsEmpty() )
1078 {
1079 ExecuteFile( editorname, fullFileName.wc_str(), nullptr, false );
1080 }
1081 }
1082}
1083
1084
1085void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1086{
1087 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1088
1089 for( PROJECT_TREE_ITEM* item_data : tree_data )
1090 item_data->Delete();
1091}
1092
1093
1094void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1095{
1096 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1097 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1098
1099 // XXX: Unnecessary?
1100 if( tree_data.size() != 1 )
1101 return;
1102
1103 wxString buffer = m_TreeProject->GetItemText( curr_item );
1104 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1105 tree_data[0]->GetFileName() );
1106 wxTextEntryDialog dlg( wxGetTopLevelParent( this ), msg, _( "Change filename" ), buffer );
1107
1108 if( dlg.ShowModal() != wxID_OK )
1109 return; // canceled by user
1110
1111 buffer = dlg.GetValue();
1112 buffer.Trim( true );
1113 buffer.Trim( false );
1114
1115 if( buffer.IsEmpty() )
1116 return; // empty file name not allowed
1117
1118 tree_data[0]->Rename( buffer, true );
1119 m_isRenaming = true;
1120}
1121
1122
1123void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1124{
1125 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1126
1127 if( tree_data.size() != 1 )
1128 return;
1129
1130 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1131 // there will be more events at least on Windows in this frame that will steal focus from
1132 // any newly launched windows
1133 m_selectedItem = tree_data[0];
1134}
1135
1136
1137void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1138{
1139 // Idle executes once all other events finished processing. This makes it ideal to launch
1140 // a new window without starting Focus wars.
1141 if( m_watcherNeedReset )
1142 {
1143 m_selectedItem = nullptr;
1145 }
1146
1147 if( m_selectedItem != nullptr )
1148 {
1149 // Activate launches a window which may run the event loop on top of us and cause OnIdle
1150 // to get called again, so be sure to block off the activation condition first.
1152 m_selectedItem = nullptr;
1153
1154 item->Activate( this );
1155 }
1156}
1157
1158
1159void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1160{
1161 wxTreeItemId itemId = Event.GetItem();
1162 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1163
1164 if( !tree_data )
1165 return;
1166
1167 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1168 return;
1169
1170 // explore list of non populated subdirs, and populate them
1171 wxTreeItemIdValue cookie;
1172 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1173
1174#ifndef __WINDOWS__
1175 bool subdir_populated = false;
1176#endif
1177
1178 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1179 {
1180 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1181
1182 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1183 continue;
1184
1185 if( itemData->IsPopulated() )
1186 continue;
1187
1188 wxString fileName = itemData->GetFileName();
1189 wxDir dir( fileName );
1190
1191 if( dir.IsOpened() )
1192 {
1193 std::vector<wxString> projects = getProjects( dir );
1194 wxString dir_filename;
1195 bool haveFile = dir.GetFirst( &dir_filename );
1196
1197 while( haveFile )
1198 {
1199 // Add name to tree item, but do not recurse in subdirs:
1200 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1201 addItemToProjectTree( name, kid, &projects, false );
1202
1203 haveFile = dir.GetNext( &dir_filename );
1204 }
1205
1206 itemData->SetPopulated( true ); // set state to populated
1207
1208#ifndef __WINDOWS__
1209 subdir_populated = true;
1210#endif
1211 }
1212
1213 // Sort filenames by alphabetic order
1214 m_TreeProject->SortChildren( kid );
1215 }
1216
1217#ifndef __WINDOWS__
1218 if( subdir_populated )
1219 m_watcherNeedReset = true;
1220#endif
1221}
1222
1223
1224std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1225{
1226 wxArrayTreeItemIds selection;
1227 std::vector<PROJECT_TREE_ITEM*> data;
1228
1229 m_TreeProject->GetSelections( selection );
1230
1231 for( auto it = selection.begin(); it != selection.end(); it++ )
1232 {
1233 PROJECT_TREE_ITEM* item = GetItemIdData( *it );
1234
1235 if( !item )
1236 {
1237 wxLogTrace( traceGit, wxS( "Null tree item returned for selection, dynamic_cast "
1238 "failed?" ) );
1239 continue;
1240 }
1241
1242 data.push_back( item );
1243 }
1244
1245 return data;
1246}
1247
1248
1250{
1251 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1252}
1253
1254
1255wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1256{
1257 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1258
1259 // If the subdir is the current working directory, return m_root
1260 // in main list:
1261 if( prj_dir == aSubDir )
1262 return m_root;
1263
1264 // The subdir is in the main tree or in a subdir: Locate it
1265 wxTreeItemIdValue cookie;
1266 wxTreeItemId root_id = m_root;
1267 std::stack<wxTreeItemId> subdirs_id;
1268
1269 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1270
1271 while( true )
1272 {
1273 if( ! child.IsOk() )
1274 {
1275 if( subdirs_id.empty() ) // all items were explored
1276 {
1277 root_id = child; // Not found: return an invalid wxTreeItemId
1278 break;
1279 }
1280 else
1281 {
1282 root_id = subdirs_id.top();
1283 subdirs_id.pop();
1284 child = m_TreeProject->GetFirstChild( root_id, cookie );
1285
1286 if( !child.IsOk() )
1287 continue;
1288 }
1289 }
1290
1291 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1292
1293 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1294 {
1295 if( itemData->GetFileName() == aSubDir ) // Found!
1296 {
1297 root_id = child;
1298 break;
1299 }
1300
1301 // child is a subdir, push in list to explore it later
1302 if( itemData->IsPopulated() )
1303 subdirs_id.push( child );
1304 }
1305
1306 child = m_TreeProject->GetNextChild( root_id, cookie );
1307 }
1308
1309 return root_id;
1310}
1311
1312
1313void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1314{
1315 // No need to process events when we're shutting down
1316 if( !m_watcher )
1317 return;
1318
1319 // Ignore events that are not file creation, deletion, renaming or modification because
1320 // they are not relevant to the project tree.
1321 if( !( event.GetChangeType() & ( wxFSW_EVENT_CREATE |
1322 wxFSW_EVENT_DELETE |
1323 wxFSW_EVENT_RENAME |
1324 wxFSW_EVENT_MODIFY ) ) )
1325 {
1326 return;
1327 }
1328
1329 const wxFileName& pathModified = event.GetPath();
1330 wxString subdir = pathModified.GetPath();
1331 wxString fn = pathModified.GetFullPath();
1332
1333 // Adjust directories to look like a file item (path and name).
1334 if( pathModified.GetFullName().IsEmpty() )
1335 {
1336 subdir = subdir.BeforeLast( '/' );
1337 fn = fn.BeforeLast( '/' );
1338 }
1339
1340 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1341
1342 if( !root_id.IsOk() )
1343 return;
1344
1345 CallAfter( [this] ()
1346 {
1347 wxLogTrace( traceGit, wxS( "File system event detected, updating tree cache" ) );
1348 m_gitStatusTimer.Start( 1500, wxTIMER_ONE_SHOT );
1349 } );
1350
1351 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1352 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1353
1354 switch( event.GetChangeType() )
1355 {
1356 case wxFSW_EVENT_CREATE:
1357 {
1358 wxTreeItemId newitem = addItemToProjectTree( fn, root_id, nullptr, true );
1359
1360 // If we are in the process of renaming a file, select the new one
1361 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1362 // pair of DELETE and CREATE events.
1363 if( m_isRenaming && newitem.IsOk() )
1364 {
1365 m_TreeProject->SelectItem( newitem );
1366 m_isRenaming = false;
1367 }
1368 }
1369 break;
1370
1371 case wxFSW_EVENT_DELETE:
1372 while( kid.IsOk() )
1373 {
1374 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1375
1376 if( itemData && itemData->GetFileName() == fn )
1377 {
1378 m_TreeProject->Delete( kid );
1379 return;
1380 }
1381 kid = m_TreeProject->GetNextChild( root_id, cookie );
1382 }
1383 break;
1384
1385 case wxFSW_EVENT_RENAME :
1386 {
1387 const wxFileName& newpath = event.GetNewPath();
1388 wxString newdir = newpath.GetPath();
1389 wxString newfn = newpath.GetFullPath();
1390
1391 while( kid.IsOk() )
1392 {
1393 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1394
1395 if( itemData && itemData->GetFileName() == fn )
1396 {
1397 m_TreeProject->Delete( kid );
1398 break;
1399 }
1400
1401 kid = m_TreeProject->GetNextChild( root_id, cookie );
1402 }
1403
1404 // Add the new item only if it is not the current project file (root item).
1405 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1406 // called after an actual file rename, and the cleanup code does not explore the
1407 // root item, because it cannot be renamed by the user. Also, ensure the new file
1408 // actually exists on the file system before it is readded. On Linux, moving a file
1409 // to the trash can cause the same path to be returned in both the old and new paths
1410 // of the event, even though the file isn't there anymore.
1411 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1412
1413 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1414 {
1415 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1416 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1417
1418 // If the item exists, select it
1419 if( newitem.IsOk() )
1420 m_TreeProject->SelectItem( newitem );
1421 }
1422
1423 m_isRenaming = false;
1424 }
1425 break;
1426
1427 default:
1428 return;
1429 }
1430
1431 // Sort filenames by alphabetic order
1432 m_TreeProject->SortChildren( root_id );
1433}
1434
1435
1437{
1438 m_watcherNeedReset = false;
1439
1440 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1441
1442#if defined( _WIN32 )
1443 KISTATUSBAR* statusBar = static_cast<KISTATUSBAR*>( m_Parent->GetStatusBar() );
1444
1445 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1446 {
1447 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1448 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1449 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1450 // Windows Server shares :/
1451 m_Parent->m_FileWatcherInfo = _( "Network path: not monitoring folder changes" );
1453 return;
1454 }
1455 else
1456 {
1457 m_Parent->m_FileWatcherInfo = _( "Local path: monitoring folder changes" );
1459 }
1460#endif
1461
1462 // Prepare file watcher:
1463 if( m_watcher )
1464 {
1465 m_watcher->RemoveAll();
1466 }
1467 else
1468 {
1470 m_watcher->SetOwner( this );
1471 }
1472
1473 // We can see wxString under a debugger, not a wxFileName
1474 wxFileName fn;
1475 fn.AssignDir( prj_dir );
1476 fn.DontFollowLink();
1477
1478 // Add directories which should be monitored.
1479 // under windows, we add the curr dir and all subdirs
1480 // under unix, we add only the curr dir and the populated subdirs
1481 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1482 // under unix, the file watcher needs more work to be efficient
1483 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1484 {
1485 wxLogNull logNo; // avoid log messages
1486#ifdef __WINDOWS__
1487 if( ! m_watcher->AddTree( fn ) )
1488 {
1489 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1490 TO_UTF8( fn.GetFullPath() ) );
1491 return;
1492 }
1493 }
1494#else
1495 if( !m_watcher->Add( fn ) )
1496 {
1497 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1498 TO_UTF8( fn.GetFullPath() ) );
1499 return;
1500 }
1501 }
1502
1503 if( m_TreeProject->IsEmpty() )
1504 return;
1505
1506 // Add subdirs
1507 wxTreeItemIdValue cookie;
1508 wxTreeItemId root_id = m_root;
1509
1510 std::stack < wxTreeItemId > subdirs_id;
1511
1512 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1513 int total_watch_count = 0;
1514
1515 while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1516 {
1517 if( !kid.IsOk() )
1518 {
1519 if( subdirs_id.empty() ) // all items were explored
1520 {
1521 break;
1522 }
1523 else
1524 {
1525 root_id = subdirs_id.top();
1526 subdirs_id.pop();
1527 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1528
1529 if( !kid.IsOk() )
1530 continue;
1531 }
1532 }
1533
1534 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1535
1536 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1537 {
1538 // we can see wxString under a debugger, not a wxFileName
1539 const wxString& path = itemData->GetFileName();
1540
1541 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1542
1543 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1544 {
1545 {
1546 // Silence OS errors that come from the watcher
1547 wxLogNull silence;
1548 fn.AssignDir( path );
1549 m_watcher->Add( fn );
1550 total_watch_count++;
1551 }
1552
1553 // if kid is a subdir, push in list to explore it later
1554 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1555 subdirs_id.push( kid );
1556 }
1557 }
1558
1559 kid = m_TreeProject->GetNextChild( root_id, cookie );
1560 }
1561
1562 if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1563 wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
1564#endif
1565
1566#if defined(DEBUG) && 1
1567 wxArrayString paths;
1568 m_watcher->GetWatchedPaths( &paths );
1569 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1570
1571 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1572 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1573#endif
1574 }
1575
1576
1578{
1579 // Make sure we don't try to inspect the tree after we've deleted its items.
1581
1582 m_TreeProject->DeleteAllItems();
1583
1584 // Remove the git repository when the project is unloaded
1585 if( m_TreeProject->GetGitRepo() )
1586 {
1587 git_repository_free( m_TreeProject->GetGitRepo() );
1588 m_TreeProject->SetGitRepo( nullptr );
1589 }
1590}
1591
1592
1593void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1594{
1597 m_TreeProject->Refresh();
1598
1599 aEvent.Skip();
1600}
1601
1602
1603void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1604{
1605 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1606 wxPaintDC dc( this );
1607
1608 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1609 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1610
1611 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1612 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1613}
1614
1615
1616void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1617{
1619}
1620
1621
1622void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1623{
1624 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1625
1626 wxString dir = tree_data->GetDir();
1627
1628 if( dir.empty() )
1629 {
1630 wxLogError( "Failed to initialize git project: project directory is empty." );
1631 return;
1632 }
1633
1634 // Check if the directory is already a git repository
1635 git_repository* repo = nullptr;
1636 int error = git_repository_open( &repo, dir.mb_str() );
1637
1638 if( error == 0 )
1639 {
1640 // Directory is already a git repository
1641 wxWindow* topLevelParent = wxGetTopLevelParent( this );
1642
1643 DisplayInfoMessage( topLevelParent,
1644 _( "The selected directory is already a Git project." ) );
1645 git_repository_free( repo );
1646 return;
1647 }
1648
1649 KIGIT::GitRepositoryPtr repoPtr( repo );
1650 DIALOG_GIT_REPOSITORY dlg( wxGetTopLevelParent( this ), nullptr );
1651
1652 dlg.SetTitle( _( "Set default remote" ) );
1653
1654 if( dlg.ShowModal() != wxID_OK )
1655 return;
1656
1657 // Directory is not a git repository
1658 if( git_repository_init( &repo, dir.mb_str(), 0 ) != GIT_OK )
1659 {
1660 if( m_gitLastError != git_error_last()->klass )
1661 {
1662 m_gitLastError = git_error_last()->klass;
1663 DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
1665 }
1666
1667 return;
1668 }
1669 else
1670 {
1671 m_TreeProject->SetGitRepo( repoPtr.release() );
1672 m_gitLastError = GIT_ERROR_NONE;
1673 }
1674
1675 //Set up the git remote
1679
1680 git_remote* remote = nullptr;
1681 wxString fullURL;
1682
1684 {
1685 fullURL = dlg.GetUsername() + "@" + dlg.GetRepoURL();
1686 }
1688 {
1689 fullURL = dlg.GetRepoURL().StartsWith( "https" ) ? "https://" : "http://";
1690
1691 if( !dlg.GetUsername().empty() )
1692 {
1693 fullURL.append( dlg.GetUsername() );
1694
1695 if( !dlg.GetPassword().empty() )
1696 {
1697 fullURL.append( wxS( ":" ) );
1698 fullURL.append( dlg.GetPassword() );
1699 }
1700
1701 fullURL.append( wxS( "@" ) );
1702 }
1703
1704 fullURL.append( dlg.GetBareRepoURL() );
1705 }
1706 else
1707 {
1708 fullURL = dlg.GetRepoURL();
1709 }
1710
1711
1712 error = git_remote_create_with_fetchspec( &remote, repo, "origin",
1713 fullURL.ToStdString().c_str(),
1714 "+refs/heads/*:refs/remotes/origin/*" );
1715
1716 if( error != GIT_OK )
1717 {
1718 if( m_gitLastError != git_error_last()->klass )
1719 {
1720 m_gitLastError = git_error_last()->klass;
1721 DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
1723 }
1724
1725 return;
1726 }
1727
1728 m_gitLastError = GIT_ERROR_NONE;
1729
1731
1732 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
1733 _( "Fetching Remote" ),
1734 1 ) );
1735
1736 handler.PerformFetch();
1737
1741
1745 Prj().GetLocalSettings().m_GitRepoType = "https";
1746 else
1747 Prj().GetLocalSettings().m_GitRepoType = "local";
1748}
1749
1750
1751void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
1752{
1753
1754}
1755
1756
1757void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
1758{
1759 git_repository* repo = m_TreeProject->GetGitRepo();
1760
1761 if( !repo )
1762 return;
1763
1765
1766 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
1767 _( "Fetching Remote" ),
1768 1 ) );
1769
1770 if( handler.PerformPull() < PullResult::Success )
1771 {
1772 wxString errorMessage = handler.GetErrorString();
1773
1774 DisplayErrorMessage( m_parent, _( "Failed to pull project" ), errorMessage );
1775 }
1776
1777 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1778}
1779
1780
1781void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1782{
1783 git_repository* repo = m_TreeProject->GetGitRepo();
1784
1785 if( !repo )
1786 return;
1787
1789
1790 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
1791 _( "Fetching Remote" ),
1792 1 ) );
1793
1794 if( handler.PerformPush() != PushResult::Success )
1795 {
1796 wxString errorMessage = handler.GetErrorString();
1797
1798 DisplayErrorMessage( m_parent, _( "Failed to push project" ), errorMessage );
1799 }
1800
1801 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1802}
1803
1804
1805static int git_create_branch( git_repository* aRepo, wxString& aBranchName )
1806{
1807 git_oid head_oid;
1808
1809 if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ) != GIT_OK )
1810 {
1811 wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s", KIGIT_COMMON::GetLastGitError() );
1812 return error;
1813 }
1814
1815 // Lookup the current commit object
1816 git_commit* commit = nullptr;
1817
1818 if( int error = git_commit_lookup( &commit, aRepo, &head_oid ) != GIT_OK )
1819 {
1820 wxLogTrace( traceGit, "Failed to lookup commit: %s", KIGIT_COMMON::GetLastGitError() );
1821 return error;
1822 }
1823
1824 KIGIT::GitCommitPtr commitPtr( commit );
1825 git_reference* branchRef = nullptr;
1826
1827 if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ) != GIT_OK )
1828 {
1829 wxLogTrace( traceGit, "Failed to create branch: %s", KIGIT_COMMON::GetLastGitError() );
1830 return error;
1831 }
1832
1833 git_reference_free( branchRef );
1834
1835 return 0;
1836}
1837
1838
1839void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1840{
1841 git_repository* repo = m_TreeProject->GetGitRepo();
1842
1843 if( !repo )
1844 return;
1845
1846 wxString branchName;
1847
1848 if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
1849 {
1850 DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), repo );
1851
1852 int retval = dlg.ShowModal();
1853 branchName = dlg.GetBranchName();
1854
1855 if( retval == wxID_ADD )
1856 git_create_branch( repo, branchName );
1857 else if( retval != wxID_OK )
1858 return;
1859 }
1860 else
1861 {
1862 std::vector<wxString> branches = m_TreeProject->GitCommon()->GetBranchNames();
1863 int branchIndex = aEvent.GetId() - ID_GIT_SWITCH_BRANCH;
1864
1865 if( branchIndex < 0 || static_cast<size_t>( branchIndex ) >= branches.size() )
1866 return;
1867
1868 branchName = branches[branchIndex];
1869 }
1870
1871 // Retrieve the reference to the existing branch using libgit2
1872 git_reference* branchRef = nullptr;
1873
1874 if( git_reference_lookup( &branchRef, repo, branchName.mb_str() ) != GIT_OK &&
1875 git_reference_dwim( &branchRef, repo, branchName.mb_str() ) != GIT_OK )
1876 {
1877 wxString errorMessage = wxString::Format( _( "Failed to lookup branch '%s': %s" ),
1878 branchName, KIGIT_COMMON::GetLastGitError() );
1879 DisplayError( m_parent, errorMessage );
1880 return;
1881 }
1882
1883 KIGIT::GitReferencePtr branchRefPtr( branchRef );
1884 const char* branchRefName = git_reference_name( branchRef );
1885 git_object* branchObj = nullptr;
1886
1887 if( git_revparse_single( &branchObj, repo, branchName.mb_str() ) != 0 )
1888 {
1889 wxString errorMessage =
1890 wxString::Format( _( "Failed to find branch head for '%s'" ), branchName );
1891 DisplayError( m_parent, errorMessage );
1892 return;
1893 }
1894
1895 KIGIT::GitObjectPtr branchObjPtr( branchObj );
1896
1897 // Switch to the branch
1898 if( git_checkout_tree( repo, branchObj, nullptr ) != 0 )
1899 {
1900 wxString errorMessage =
1901 wxString::Format( _( "Failed to switch to branch '%s'" ), branchName );
1902 DisplayError( m_parent, errorMessage );
1903 return;
1904 }
1905
1906 // Update the HEAD reference
1907 if( git_repository_set_head( repo, branchRefName ) != 0 )
1908 {
1909 wxString errorMessage = wxString::Format(
1910 _( "Failed to update HEAD reference for branch '%s'" ), branchName );
1911 DisplayError( m_parent, errorMessage );
1912 return;
1913 }
1914}
1915
1916
1917void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1918{
1919 git_repository* repo = m_TreeProject->GetGitRepo();
1920
1921 if( !repo
1922 || !IsOK( wxGetTopLevelParent( this ),
1923 _( "Are you sure you want to remove Git tracking from this project?" ) ) )
1924 {
1925 return;
1926 }
1927
1928 // Remove the VCS (git) from the project directory
1929 git_repository_free( repo );
1930 m_TreeProject->SetGitRepo( nullptr );
1931
1932 // Remove the .git directory
1933 wxFileName fn( m_Parent->GetProjectFileName() );
1934 fn.AppendDir( ".git" );
1935
1936 wxString errors;
1937
1938 if( !RmDirRecursive( fn.GetPath(), &errors ) )
1939 {
1940 DisplayErrorMessage( m_parent, _( "Failed to remove Git directory" ), errors );
1941 }
1942
1943 // Clear all item states
1944 std::stack<wxTreeItemId> items;
1945 items.push( m_TreeProject->GetRootItem() );
1946
1947 while( !items.empty() )
1948 {
1949 wxTreeItemId current = items.top();
1950 items.pop();
1951
1952 // Process the current item
1953 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1954
1955 wxTreeItemIdValue cookie;
1956 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1957
1958 while( child.IsOk() )
1959 {
1960 items.push( child );
1961 child = m_TreeProject->GetNextChild( current, cookie );
1962 }
1963 }
1964}
1965
1966
1968{
1969 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Updating git status icons" ) );
1970 std::unique_lock<std::mutex> lock( m_gitStatusMutex, std::try_to_lock );
1971
1972 if( !lock.owns_lock() )
1973 {
1974 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Failed to acquire lock for git status icon update" ) );
1975 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1976 return;
1977 }
1978
1979 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
1980 {
1981 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git is disabled or tree control is null" ) );
1982 return;
1983 }
1984
1985 std::stack<wxTreeItemId> items;
1986 items.push(m_TreeProject->GetRootItem());
1987
1988 while( !items.empty() )
1989 {
1990 wxTreeItemId current = items.top();
1991 items.pop();
1992
1993 if( m_TreeProject->ItemHasChildren( current ) )
1994 {
1995 wxTreeItemIdValue cookie;
1996 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1997
1998 while( child.IsOk() )
1999 {
2000 items.push( child );
2001
2002 if( auto it = m_gitStatusIcons.find( child ); it != m_gitStatusIcons.end() )
2003 {
2004 m_TreeProject->SetItemState( child, static_cast<int>( it->second ) );
2005 }
2006
2007 child = m_TreeProject->GetNextChild( current, cookie );
2008 }
2009 }
2010 }
2011
2012 if (!m_gitCurrentBranchName.empty())
2013 {
2014 wxTreeItemId kid = m_TreeProject->GetRootItem();
2015 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
2016 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
2017 m_TreeProject->SetItemText( kid, filename + " [" + m_gitCurrentBranchName + "]" );
2018 m_gitIconsInitialized = true;
2019 }
2020
2021 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git status icons updated" ) );
2022}
2023
2024
2026{
2027 wxLogTrace( traceGit, wxS( "updateTreeCache: Updating tree cache" ) );
2028
2029 std::unique_lock<std::mutex> lock( m_gitTreeCacheMutex, std::try_to_lock );
2030
2031 if( !lock.owns_lock() )
2032 {
2033 wxLogTrace( traceGit, wxS( "updateTreeCache: Failed to acquire lock for tree cache update" ) );
2034 return;
2035 }
2036
2037 if( !m_TreeProject )
2038 {
2039 wxLogTrace( traceGit, wxS( "updateTreeCache: Tree control is null" ) );
2040 return;
2041 }
2042
2043 wxTreeItemId kid = m_TreeProject->GetRootItem();
2044
2045 if( !kid.IsOk() )
2046 return;
2047
2048 // Collect a map to easily set the state of each item
2049 m_gitTreeCache.clear();
2050 std::stack<wxTreeItemId> items;
2051 items.push( kid );
2052
2053 while( !items.empty() )
2054 {
2055 kid = items.top();
2056 items.pop();
2057
2058 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
2059
2060 wxString gitAbsPath = nextItem->GetFileName();
2061#ifdef _WIN32
2062 gitAbsPath.Replace( wxS( "\\" ), wxS( "/" ) );
2063#endif
2064 m_gitTreeCache[gitAbsPath] = kid;
2065
2066 wxTreeItemIdValue cookie;
2067 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
2068
2069 while( child.IsOk() )
2070 {
2071 items.push( child );
2072 child = m_TreeProject->GetNextChild( kid, cookie );
2073 }
2074 }
2075}
2076
2077
2079{
2080 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updating git status icons" ) );
2081#if defined( _WIN32 )
2083
2084 if( refresh != 0
2086 {
2087 // Need to treat windows network paths special here until we get the samba bug fixed
2088 // https://github.com/wxWidgets/wxWidgets/issues/18953
2089 CallAfter(
2090 [this, refresh]()
2091 {
2092 m_gitStatusTimer.Start( refresh, wxTIMER_ONE_SHOT );
2093 } );
2094 }
2095
2096#endif
2097
2098 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2099 return;
2100
2101 std::unique_lock<std::mutex> lock1( m_gitStatusMutex, std::try_to_lock );
2102 std::unique_lock<std::mutex> lock2( m_gitTreeCacheMutex, std::try_to_lock );
2103
2104 if( !lock1.owns_lock() || !lock2.owns_lock() )
2105 {
2106 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire locks for git status icon update" ) );
2107 return;
2108 }
2109
2110 git_repository* repo = m_TreeProject->GetGitRepo();
2111
2112 if( !repo )
2113 {
2114 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: No git repository found" ) );
2115 return;
2116 }
2117
2118 // Get Current Branch
2119 PROJECT_TREE_ITEM* rootItem = GetItemIdData( m_TreeProject->GetRootItem() );
2120 wxFileName rootFilename( rootItem->GetFileName() );
2121 wxString repoWorkDir( git_repository_workdir( repo ) );
2122
2123 wxFileName relative = rootFilename;
2124 relative.MakeRelativeTo( repoWorkDir );
2125 wxString pathspecStr = relative.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
2126
2127#ifdef _WIN32
2128 pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
2129#endif
2130
2131 const char* pathspec[] = { pathspecStr.c_str().AsChar() };
2132
2133 git_status_options status_options;
2134 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
2135 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2136 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
2137 status_options.pathspec = { (char**) pathspec, 1 };
2138
2139 git_index* index = nullptr;
2140
2141 if( git_repository_index( &index, repo ) != GIT_OK )
2142 {
2143 m_gitLastError = giterr_last()->klass;
2144 wxLogTrace( traceGit, wxS( "Failed to get git index: %s" ), KIGIT_COMMON::GetLastGitError() );
2145 return;
2146 }
2147
2148 KIGIT::GitIndexPtr indexPtr( index );
2149 git_status_list* status_list = nullptr;
2150
2151 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
2152 {
2153 wxLogTrace( traceGit, wxS( "Failed to get git status list: %s" ), KIGIT_COMMON::GetLastGitError() );
2154 return;
2155 }
2156
2157 KIGIT::GitStatusListPtr statusListPtr( status_list );
2158 auto [localChanges, remoteChanges] = m_TreeProject->GitCommon()->GetDifferentFiles();
2159
2160 size_t count = git_status_list_entrycount( status_list );
2161 bool updated = false;
2162
2163 for( size_t ii = 0; ii < count; ++ii )
2164 {
2165 const git_status_entry* entry = git_status_byindex( status_list, ii );
2166 std::string path( entry->head_to_index? entry->head_to_index->old_file.path
2167 : entry->index_to_workdir->old_file.path );
2168
2169 wxString absPath = repoWorkDir;
2170 absPath << path;
2171
2172 auto iter = m_gitTreeCache.find( absPath );
2173
2174 if( iter == m_gitTreeCache.end() )
2175 {
2176 wxLogTrace( traceGit, wxS( "File '%s' not found in tree cache" ), absPath );
2177 continue;
2178 }
2179
2180 // If we are current, don't continue because we still need to check to see if the
2181 // current commit is ahead/behind the remote. If the file is modified/added/deleted,
2182 // that is the main status we want to show.
2183 if( entry->status & GIT_STATUS_IGNORED )
2184 {
2185 wxLogTrace( traceGit, wxS( "File '%s' is ignored" ), absPath );
2186 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2188
2189 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
2190 updated = true;
2191
2193 }
2194 else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
2195 {
2196 wxLogTrace( traceGit, wxS( "File '%s' is modified in %s" ),
2197 absPath, ( entry->status & GIT_STATUS_INDEX_MODIFIED )? "index" : "working tree" );
2198 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2200
2201 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED )
2202 updated = true;
2203
2205 }
2206 else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
2207 {
2208 wxLogTrace( traceGit, wxS( "File '%s' is new in %s" ),
2209 absPath, ( entry->status & GIT_STATUS_INDEX_NEW )? "index" : "working tree" );
2210 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2212
2213 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED )
2214 updated = true;
2215
2217 }
2218 else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
2219 {
2220 wxLogTrace( traceGit, wxS( "File '%s' is deleted in %s" ),
2221 absPath, ( entry->status & GIT_STATUS_INDEX_DELETED )? "index" : "working tree" );
2222 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2224
2225 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED )
2226 updated = true;
2227
2229 }
2230 else if( localChanges.count( path ) )
2231 {
2232 wxLogTrace( traceGit, wxS( "File '%s' is ahead of remote" ), absPath );
2233 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2235
2236 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD )
2237 updated = true;
2238
2240 }
2241 else if( remoteChanges.count( path ) )
2242 {
2243 wxLogTrace( traceGit, wxS( "File '%s' is behind remote" ), absPath );
2244 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2246
2247 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND )
2248 updated = true;
2249
2251 }
2252 else
2253 {
2254 // If we are here, the file is unmodified and not ignored
2255 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
2257
2258 if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
2259 updated = true;
2260
2262 }
2263 }
2264
2265 git_reference* currentBranchReference = nullptr;
2266 int rc = git_repository_head( &currentBranchReference, repo );
2267 KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
2268
2269 // Get the current branch name
2270 if( currentBranchReference )
2271 {
2272 m_gitCurrentBranchName = git_reference_shorthand( currentBranchReference );
2273 }
2274 else if( rc == GIT_EUNBORNBRANCH )
2275 {
2276 // TODO: couldn't immediately figure out if libgit2 can return the name of an unborn branch
2277 // For now, just do nothing
2278 }
2279 else
2280 {
2281 if( giterr_last()->klass != m_gitLastError )
2282 wxLogTrace( "git", "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
2283
2284 m_gitLastError = giterr_last()->klass;
2285 }
2286
2287 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updated git status icons" ) );
2288 // If the icons are not changed, queue an event to update in the main thread
2289 if( updated || !m_gitIconsInitialized )
2290 {
2291 CallAfter(
2292 [this]()
2293 {
2295 } );
2296 }
2297}
2298
2299
2300void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2301{
2302 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2303
2304 git_repository* repo = m_TreeProject->GetGitRepo();
2305
2306 if( repo == nullptr )
2307 {
2308 wxMessageBox( _( "The selected directory is not a Git project." ) );
2309 return;
2310 }
2311
2312 git_config* config = nullptr;
2313 git_repository_config( &config, repo );
2314 KIGIT::GitConfigPtr configPtr( config );
2315
2316 // Read relevant data from the git config
2317 wxString authorName;
2318 wxString authorEmail;
2319
2320 // Read author name
2321 git_config_entry* name_c = nullptr;
2322 git_config_entry* email_c = nullptr;
2323 int authorNameError = git_config_get_entry( &name_c, config, "user.name" );
2324 KIGIT::GitConfigEntryPtr namePtr( name_c );
2325
2326 if( authorNameError != 0 || name_c == nullptr )
2327 {
2328 authorName = Pgm().GetCommonSettings()->m_Git.authorName;
2329 }
2330 else
2331 {
2332 authorName = name_c->value;
2333 }
2334
2335 // Read author email
2336 int authorEmailError = git_config_get_entry( &email_c, config, "user.email" );
2337
2338 if( authorEmailError != 0 || email_c == nullptr )
2339 {
2340 authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
2341 }
2342 else
2343 {
2344 authorEmail = email_c->value;
2345 }
2346
2347 // Collect modified files in the repository
2348 git_status_options status_options;
2349 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
2350 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2351 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
2352
2353 git_status_list* status_list = nullptr;
2354 git_status_list_new( &status_list, repo, &status_options );
2355 KIGIT::GitStatusListPtr statusListPtr( status_list );
2356
2357 std::map<wxString, int> modifiedFiles;
2358
2359 size_t count = git_status_list_entrycount( status_list );
2360
2361 std::set<wxString> selected_files;
2362
2363 for( PROJECT_TREE_ITEM* item : tree_data )
2364 {
2365 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2366 selected_files.emplace( item->GetFileName() );
2367 }
2368
2369 for( size_t i = 0; i < count; ++i )
2370 {
2371 const git_status_entry* entry = git_status_byindex( status_list, i );
2372
2373 // Check if the file is modified (index or workdir changes)
2374 if( entry->status == GIT_STATUS_CURRENT
2375 || ( entry->status & ( GIT_STATUS_CONFLICTED | GIT_STATUS_IGNORED ) ) )
2376 {
2377 continue;
2378 }
2379
2380 wxFileName fn;
2381 wxString filePath;
2382
2383 // TODO: we are kind of erasing the difference between workdir and index here,
2384 // because the Commit dialog doesn't show that difference.
2385 // Entry may only have a head_to_index if it was previously staged
2386 if( entry->index_to_workdir )
2387 {
2388 fn.Assign( entry->index_to_workdir->old_file.path );
2389 fn.MakeAbsolute( git_repository_workdir( repo ) );
2390 filePath = wxString( entry->index_to_workdir->old_file.path, wxConvUTF8 );
2391 }
2392 else if( entry->head_to_index )
2393 {
2394 fn.Assign( entry->head_to_index->old_file.path );
2395 fn.MakeAbsolute( git_repository_workdir( repo ) );
2396 filePath = wxString( entry->head_to_index->old_file.path, wxConvUTF8 );
2397 }
2398 else
2399 {
2400 wxCHECK2_MSG( false, continue, "File status with neither git_status_entry set!" );
2401 }
2402
2403 // Do not commit files outside the project directory
2404 wxString projectPath = Prj().GetProjectPath();
2405 wxString fileName = fn.GetFullPath();
2406
2407 if( !fileName.StartsWith( projectPath ) )
2408 continue;
2409
2410 // Skip lock files
2411 if( fn.GetExt().CmpNoCase( FILEEXT::LockFileExtension ) == 0 )
2412 continue;
2413
2414 // Skip autosave, lock, and backup files
2415 if( fn.GetName().StartsWith( FILEEXT::AutoSaveFilePrefix )
2416 || fn.GetName().StartsWith( FILEEXT::LockFilePrefix )
2417 || fn.GetName().EndsWith( FILEEXT::BackupFileSuffix ) )
2418 {
2419 continue;
2420 }
2421
2422 // Skip archived project backups
2423 if( fn.GetPath().Contains( Prj().GetProjectName() + wxT( "-backups" ) ) )
2424 continue;
2425
2426 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2427 {
2428 modifiedFiles.emplace( filePath, entry->status );
2429 }
2430 else if( selected_files.count( fn.GetFullPath() ) )
2431 {
2432 modifiedFiles.emplace( filePath, entry->status );
2433 }
2434 }
2435
2436 // Create a commit dialog
2437 DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, authorName, authorEmail,
2438 modifiedFiles );
2439 auto ret = dlg.ShowModal();
2440
2441 if( ret != wxID_OK )
2442 return;
2443
2444 // Commit the changes
2445 git_oid tree_id;
2446 git_tree* tree = nullptr;
2447 git_commit* parent = nullptr;
2448 git_index* index = nullptr;
2449
2450 std::vector<wxString> files = dlg.GetSelectedFiles();
2451
2452 if( dlg.GetCommitMessage().IsEmpty() )
2453 {
2454 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2455 return;
2456 }
2457
2458 if( files.empty() )
2459 {
2460 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2461 return;
2462 }
2463
2464 if( git_repository_index( &index, repo ) != 0 )
2465 {
2466 wxLogTrace( traceGit, wxString::Format( _( "Failed to get repository index: %s" ),
2468 return;
2469 }
2470
2471 KIGIT::GitIndexPtr indexPtr( index );
2472
2473 for( wxString& file : files )
2474 {
2475 if( git_index_add_bypath( index, file.mb_str() ) != 0 )
2476 {
2477 wxMessageBox( wxString::Format( _( "Failed to add file to index: %s" ),
2479 return;
2480 }
2481 }
2482
2483 if( git_index_write( index ) != 0 )
2484 {
2485 wxLogTrace( traceGit, wxString::Format( _( "Failed to write index: %s" ),
2487 return;
2488 }
2489
2490 if( git_index_write_tree( &tree_id, index ) != 0)
2491 {
2492 wxLogTrace( traceGit, wxString::Format( _( "Failed to write tree: %s" ),
2494 return;
2495 }
2496
2497 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
2498 {
2499 wxLogTrace( traceGit, wxString::Format( _( "Failed to lookup tree: %s" ),
2501 return;
2502 }
2503
2504 KIGIT::GitTreePtr treePtr( tree );
2505 git_reference* headRef = nullptr;
2506
2507 if( git_repository_head_unborn( repo ) == 0 )
2508 {
2509 if( git_repository_head( &headRef, repo ) != 0 )
2510 {
2511 wxLogTrace( traceGit, wxString::Format( _( "Failed to get HEAD reference: %s" ),
2513 return;
2514 }
2515
2516 KIGIT::GitReferencePtr headRefPtr( headRef );
2517
2518 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
2519 {
2520 wxLogTrace( traceGit, wxString::Format( _( "Failed to get commit: %s" ),
2522 return;
2523 }
2524 }
2525
2526 KIGIT::GitCommitPtr parentPtr( parent );
2527 const wxString& commit_msg = dlg.GetCommitMessage();
2528 const wxString& author_name = dlg.GetAuthorName();
2529 const wxString& author_email = dlg.GetAuthorEmail();
2530
2531 git_signature* author = nullptr;
2532
2533 if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
2534 {
2535 wxLogTrace( traceGit, wxString::Format( _( "Failed to create author signature: %s" ),
2537 return;
2538 }
2539
2540 KIGIT::GitSignaturePtr authorPtr( author );
2541 git_oid oid;
2542
2543#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
2544 && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
2545 /*
2546 * For libgit2 versions 1.8.0, 1.8.1. (cf19ddc52)
2547 * This change was reverted for 1.8.2 (49d3fadfc, main branch)
2548 * The revert for 1.8.2 was not included for 1.8.3 (which is on the maint/v1.8 branch, not main)
2549 * This change was also reverted for 1.8.4 (94ba816f6, also maint/v1.8 branch)
2550 *
2551 * As of 1.8.4, the history is like this:
2552 *
2553 * * 3f4182d15 (tag: v1.8.4, maint/v1.8)
2554 * * 94ba816f6 Revert "commit: fix const declaration" [puts const back]
2555 * * 3353f78e8 (tag: v1.8.3)
2556 * | * 4ce872a0f (tag: v1.8.2-rc1, tag: v1.8.2)
2557 * | * 49d3fadfc Revert "commit: fix const declaration" [puts const back]
2558 * |/
2559 * * 36f7e21ad (tag: v1.8.1)
2560 * * d74d49148 (tag: v1.8.0)
2561 * * cf19ddc52 commit: fix const declaration [removes const]
2562 */
2563 git_commit* const parents[1] = { parent };
2564#else
2565 // For libgit2 versions older than 1.8.0, or equal to 1.8.2, or 1.8.4+
2566 const git_commit* parents[1] = { parent };
2567#endif
2568
2569 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
2570 1, parents ) != 0 )
2571 {
2572 wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
2574 return;
2575 }
2576
2577 wxLogTrace( traceGit, wxString::Format( _( "Created commit with id: %s" ),
2578 git_oid_tostr_s( &oid ) ) );
2579
2580 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2581}
2582
2583
2584void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2585{
2586
2587}
2588
2589
2590bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2591{
2592 git_index *index;
2593 size_t entry_pos;
2594
2595 git_repository* repo = m_TreeProject->GetGitRepo();
2596
2597 if( !repo )
2598 return false;
2599
2600 if( git_repository_index( &index, repo ) != 0 )
2601 {
2602 wxLogTrace( traceGit, "Failed to get git index: %s", KIGIT_COMMON::GetLastGitError() );
2603 return false;
2604 }
2605
2606 KIGIT::GitIndexPtr indexPtr( index );
2607
2608 // If we successfully find the file in the index, we may not add it to the VCS
2609 if( git_index_find( &entry_pos, index, aFile.mb_str() ) == 0 )
2610 {
2611 wxLogTrace( traceGit, "File already in index: %s", aFile );
2612 return false;
2613 }
2614
2615 return true;
2616}
2617
2618
2619void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2620{
2621 wxLogTrace( traceGit, "Syncing project" );
2622 git_repository* repo = m_TreeProject->GetGitRepo();
2623
2624 if( !repo )
2625 {
2626 wxLogTrace( traceGit, "sync: No git repository found" );
2627 return;
2628 }
2629
2630 GIT_SYNC_HANDLER handler( repo );
2631 handler.PerformSync();
2632}
2633
2634
2635void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2636{
2637 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2638
2639 if( !gitCommon )
2640 return;
2641
2642 GIT_PULL_HANDLER handler( gitCommon );
2643 handler.PerformFetch();
2644
2645 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2646}
2647
2648
2649void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2650{
2651 git_repository* repo = m_TreeProject->GetGitRepo();
2652
2653 if( !repo )
2654 return;
2655
2656 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2657 handler.PerformResolveConflict();
2658}
2659
2660
2661void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2662{
2663 git_repository* repo = m_TreeProject->GetGitRepo();
2664
2665 if( !repo )
2666 return;
2667
2668 GIT_REVERT_HANDLER handler( repo );
2669 handler.PerformRevert();
2670}
2671
2672
2673void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2674{
2675 git_repository* repo = m_TreeProject->GetGitRepo();
2676
2677 if( !repo )
2678 return;
2679
2680 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2681 handler.PerformRemoveFromIndex();
2682}
2683
2684
2686{
2687
2688}
2689
2690
2691void PROJECT_TREE_PANE::onGitSyncTimer( wxTimerEvent& aEvent )
2692{
2693 wxLogTrace( traceGit, "onGitSyncTimer" );
2694 COMMON_SETTINGS::GIT& gitSettings = Pgm().GetCommonSettings()->m_Git;
2695
2696 if( !gitSettings.enableGit || !m_TreeProject )
2697 return;
2698
2700
2701 tp.push_task( [this]()
2702 {
2703 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2704
2705 if( !gitCommon )
2706 {
2707 wxLogTrace( traceGit, "onGitSyncTimer: No git repository found" );
2708 return;
2709 }
2710
2711 GIT_PULL_HANDLER handler( gitCommon );
2712 handler.PerformFetch();
2713
2714 CallAfter( [this]()
2715 {
2717 } );
2718 } );
2719
2720 if( gitSettings.updatInterval > 0 )
2721 {
2722 wxLogTrace( traceGit, "onGitSyncTimer: Restarting git sync timer" );
2723 // We store the timer interval in minutes but wxTimer uses milliseconds
2724 m_gitSyncTimer.Start( gitSettings.updatInterval * 60 * 1000, wxTIMER_ONE_SHOT );
2725 }
2726}
2727
2728
2730{
2733
2734 tp.push_task(
2735 [this]()
2736 {
2738 } );
2739}
2740
2741void PROJECT_TREE_PANE::onGitStatusTimer( wxTimerEvent& aEvent )
2742{
2743 wxLogTrace( traceGit, "onGitStatusTimer" );
2744 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2745 return;
2746
2748}
const char * name
Definition: DXF_plotter.cpp:59
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:104
BITMAP_STORE * GetBitmapStore()
Definition: bitmap.cpp:92
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void ThemeChanged()
Notifies the store that the icon theme has been changed by the user, so caches must be invalidated.
wxString GetCommitMessage() const
wxString GetAuthorEmail() const
std::vector< wxString > GetSelectedFiles() const
wxString GetAuthorName() const
KIGIT_COMMON::GIT_CONN_TYPE GetRepoType() const
wxString GetBareRepoURL() const
Get the Bare Repo U R L object.
wxString GetRepoSSHPath() const
wxString GetUsername() const
wxString GetPassword() const
wxString GetBranchName() const
int ShowModal() override
void SetProgressReporter(std::unique_ptr< WX_PROGRESS_REPORTER > aProgressReporter)
Definition: git_progress.h:40
bool PerformFetch(bool aSkipLock=false)
PullResult PerformPull()
PushResult PerformPush()
The main KiCad project manager frame.
PROJECT_TREE_PANE * m_leftWin
const wxString GetProjectFileName() const
void OnChangeWatchedPaths(wxCommandEvent &aEvent)
Called by sending a event with id = ID_INIT_WATCHED_PATHS rebuild the list of watched paths.
void LoadProject(const wxFileName &aProjectFileName)
std::vector< wxString > GetBranchNames() const
static wxString GetLastGitError()
wxString GetGitRootDirectory() const
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
std::pair< std::set< wxString >, std::set< wxString > > GetDifferentFiles() const
Return a pair of sets of files that differ locally from the remote repository The first set is files ...
bool HasPushAndPullRemote() const
void UpdateCurrentBranchInfo()
bool HasLocalCommits() const
void SetPassword(const wxString &aPassword)
wxString GetErrorString()
KISTATUSBAR is a wxStatusBar suitable for Kicad manager.
Definition: kistatusbar.h:45
void SetEllipsedTextField(const wxString &aText, int aFieldId)
Set the text in a field using wxELLIPSIZE_MIDDLE option to adjust the text size to the field size.
static wxString GetDefaultUserProjectsPath()
Gets the default path we point users to create projects.
Definition: paths.cpp:143
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition: pgm_base.cpp:687
virtual const wxString & GetTextEditor(bool aCanShowFileChooser=true)
Return the path to the preferred text editor application.
Definition: pgm_base.cpp:196
Handle one item (a file or a directory name) for the tree file.
void SetRootFile(bool aValue)
void SetState(int state)
const wxString & GetFileName() const
bool IsPopulated() const
void SetPopulated(bool aValue)
TREE_FILE_TYPE GetType() const
const wxString GetDir() const
void Activate(PROJECT_TREE_PANE *aTreePrjFrame)
PROJECT_TREE_PANE Window to display the tree files.
PROJECT_TREE_ITEM * m_selectedItem
std::unordered_map< wxString, wxTreeItemId > m_gitTreeCache
void onGitFetch(wxCommandEvent &event)
Fetch the latest changes from the git repository.
std::map< wxTreeItemId, KIGIT_COMMON::GIT_STATUS > m_gitStatusIcons
void onGitInitializeProject(wxCommandEvent &event)
Initialize a new git repository in the current project directory.
void onGitSyncProject(wxCommandEvent &event)
Sync the current project with the git repository.
void onDeleteFile(wxCommandEvent &event)
Function onDeleteFile Delete the selected file or directory in the tree project.
void onGitRemoveVCS(wxCommandEvent &event)
Remove the git repository from the current project directory.
void EmptyTreePrj()
Delete all m_TreeProject entries.
void FileWatcherReset()
Reinit the watched paths Should be called after opening a new project to rebuild the list of watched ...
wxString m_gitCurrentBranchName
std::vector< PROJECT_TREE_ITEM * > GetSelectedData()
Function GetSelectedData return the item data from item currently selected (highlighted) Note this is...
void onOpenDirectory(wxCommandEvent &event)
Function onOpenDirectory Handles the right-click menu for opening a directory in the current system f...
void onGitResolveConflict(wxCommandEvent &event)
Resolve conflicts in the git repository.
void onFileSystemEvent(wxFileSystemWatcherEvent &event)
called when a file or directory is modified/created/deleted The tree project is modified when a file ...
wxFileSystemWatcher * m_watcher
void onRight(wxTreeEvent &Event)
Called on a right click on an item.
void onGitCommit(wxCommandEvent &event)
Commit the current project saved changes to the git repository.
void onSelect(wxTreeEvent &Event)
Called on a double click on an item.
PROJECT_TREE * m_TreeProject
KICAD_MANAGER_FRAME * m_Parent
void onExpand(wxTreeEvent &Event)
Called on a click on the + or - button of an item with children.
void onGitSyncTimer(wxTimerEvent &event)
void onIdle(wxIdleEvent &aEvent)
Idle event handler, used process the selected items at a point in time when all other events have bee...
void updateTreeCache()
Updates the map of the wxtreeitemid to the name of each file for use in the thread.
std::mutex m_gitStatusMutex
void onGitRevertLocal(wxCommandEvent &event)
Revert the local repository to the last commit.
void onGitPullProject(wxCommandEvent &event)
Pull the latest changes from the git repository.
static wxString GetFileExt(TREE_FILE_TYPE type)
friend class PROJECT_TREE_ITEM
void onGitRemoveFromIndex(wxCommandEvent &event)
Remove a file from the git index.
wxTreeItemId findSubdirTreeItem(const wxString &aSubDir)
Function findSubdirTreeItem searches for the item in tree project which is the node of the subdirecto...
void ReCreateTreePrj()
Create or modify the tree showing project file names.
void onThemeChanged(wxSysColourChangedEvent &aEvent)
void onRunSelectedJobsFile(wxCommandEvent &event)
Run a selected jobs file.
void onGitCompare(wxCommandEvent &event)
Compare the current project to a different branch in the git repository.
void onGitStatusTimer(wxTimerEvent &event)
void shutdownFileWatcher()
Shutdown the file watcher.
std::mutex m_gitTreeCacheMutex
wxTreeItemId addItemToProjectTree(const wxString &aName, const wxTreeItemId &aParent, std::vector< wxString > *aProjectNames, bool aRecurse)
Function addItemToProjectTree.
bool hasChangedFiles()
Returns true if the current project has any uncommitted changes.
void onOpenSelectedFileWithTextEditor(wxCommandEvent &event)
Function onOpenSelectedFileWithTextEditor Call the text editor to open the selected file in the tree ...
void onGitAddToIndex(wxCommandEvent &event)
Add a file to the git index.
void updateGitStatusIcons()
Updates the icons shown in the tree project to reflect the current git status.
void updateGitStatusIconMap()
This is a threaded call that will change the map of git status icons for use in the main thread.
PROJECT_TREE_ITEM * GetItemIdData(wxTreeItemId aId)
Function GetItemIdData return the item data corresponding to a wxTreeItemId identifier.
void onGitSwitchBranch(wxCommandEvent &event)
Switch to a different branch in the git repository.
void onCreateNewDirectory(wxCommandEvent &event)
Function onCreateNewDirectory Creates a new subdirectory inside the current kicad project directory t...
void onSwitchToSelectedProject(wxCommandEvent &event)
Switch to a other project selected from the tree project (by selecting an other .pro file inside the ...
void onPaint(wxPaintEvent &aEvent)
We don't have uniform borders so we have to draw them ourselves.
void onRenameFile(wxCommandEvent &event)
Function onRenameFile Rename the selected file or directory in the tree project.
void onGitPushProject(wxCommandEvent &event)
Push the current project changes to the git repository.
bool canFileBeAddedToVCS(const wxString &aFilePath)
Returns true if the file has already been added to the repository or false if it has not been added y...
std::vector< wxString > m_filters
PROJECT_TREE This is the class to show (as a tree) the files in the project directory.
Definition: project_tree.h:43
git_repository * GetGitRepo() const
Definition: project_tree.h:61
KIGIT_COMMON * GitCommon() const
Definition: project_tree.h:63
void LoadIcons()
void SetGitRepo(git_repository *aRepo)
Definition: project_tree.h:60
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:146
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:209
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:249
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition: confirm.cpp:221
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:194
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:170
This file is part of the common library.
#define _(s)
EVT_MENU_RANGE(ID_GERBVIEW_DRILL_FILE1, ID_GERBVIEW_DRILL_FILEMAX, GERBVIEW_FRAME::OnDrlFileHistory) EVT_MENU_RANGE(ID_GERBVIEW_ZIP_FILE1
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:143
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition: gestfich.cpp:311
int m_GitIconRefreshInterval
The interval in milliseconds to refresh the git icons in the project tree.
static const std::string LegacySchematicFileExtension
static const std::string HtmlFileExtension
static const wxString GerberFileExtensionsRegex
static const std::string NetlistFileExtension
static const std::string GerberJobFileExtension
static const std::string ReportFileExtension
static const std::string LockFileExtension
static const std::string ProjectFileExtension
static const std::string FootprintPlaceFileExtension
static const std::string LegacyPcbFileExtension
static const std::string LegacyProjectFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string LegacySymbolLibFileExtension
static const std::string LockFilePrefix
static const std::string KiCadSymbolLibFileExtension
static const std::string SpiceFileExtension
static const std::string PdfFileExtension
static const std::string TextFileExtension
static const std::string DrawingSheetFileExtension
static const std::string BackupFileSuffix
static const std::string AutoSaveFilePrefix
static const std::string KiCadJobSetFileExtension
static const std::string FootprintAssignmentFileExtension
static const std::string SVGFileExtension
static const std::string DrillFileExtension
static const std::string DesignRulesFileExtension
static const std::string MarkdownFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string ArchiveFileExtension
static const std::string KiCadPcbFileExtension
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
const wxChar *const traceGit
Flag to enable Git debugging output.
PROJECT & Prj()
Definition: kicad.cpp:597
#define GIT_BUF_INIT
IDs used in KiCad main frame foe menuitems and tools.
@ ID_PROJECT_OPEN_DIR
Definition: kicad_id.h:62
@ ID_GIT_PUSH
Definition: kicad_id.h:80
@ ID_GIT_REVERT_LOCAL
Definition: kicad_id.h:83
@ ID_JOBS_RUN
Definition: kicad_id.h:95
@ ID_PROJECT_SWITCH_TO_OTHER
Definition: kicad_id.h:60
@ ID_PROJECT_NEWDIR
Definition: kicad_id.h:61
@ ID_PROJECT_RENAME
Definition: kicad_id.h:64
@ ID_GIT_SWITCH_QUICK5
Definition: kicad_id.h:93
@ ID_GIT_REMOVE_VCS
Definition: kicad_id.h:85
@ ID_GIT_COMMIT_FILE
Definition: kicad_id.h:77
@ ID_GIT_PULL
Definition: kicad_id.h:81
@ ID_GIT_COMPARE
Definition: kicad_id.h:84
@ ID_PROJECT_TXTEDIT
Definition: kicad_id.h:59
@ ID_GIT_REMOVE_FROM_INDEX
Definition: kicad_id.h:87
@ ID_GIT_RESOLVE_CONFLICT
Definition: kicad_id.h:82
@ ID_GIT_COMMIT_PROJECT
Definition: kicad_id.h:76
@ ID_GIT_SWITCH_BRANCH
Definition: kicad_id.h:88
@ ID_GIT_ADD_TO_INDEX
Definition: kicad_id.h:86
@ ID_GIT_SYNC_PROJECT
Definition: kicad_id.h:78
@ ID_PROJECT_TREE
Definition: kicad_id.h:58
@ ID_GIT_FETCH
Definition: kicad_id.h:79
@ ID_GIT_SWITCH_QUICK1
Definition: kicad_id.h:89
@ ID_GIT_INITIALIZE_PROJECT
Definition: kicad_id.h:74
@ ID_PROJECT_DELETE
Definition: kicad_id.h:63
@ ID_LEFT_FRAME
Definition: kicad_id.h:57
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
Definition: launch_ext.cpp:25
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
std::unique_ptr< git_object, decltype([](git_object *aObject) { git_object_free(aObject) GitObjectPtr
A unique pointer for git_object objects with automatic cleanup.
std::unique_ptr< git_status_list, decltype([](git_status_list *aList) { git_status_list_free(aList) GitStatusListPtr
A unique pointer for git_status_list objects with automatic cleanup.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit) GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
std::unique_ptr< git_buf, decltype([](git_buf *aBuf) { git_buf_free(aBuf) GitBufPtr
A unique pointer for git_buf objects with automatic cleanup.
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::unique_ptr< git_config_entry, decltype([](git_config_entry *aEntry) { git_config_entry_free(aEntry) GitConfigEntryPtr
A unique pointer for git_config_entry objects with automatic cleanup.
std::unique_ptr< git_index, decltype([](git_index *aIndex) { git_index_free(aIndex) GitIndexPtr
A unique pointer for git_index objects with automatic cleanup.
std::unique_ptr< git_signature, decltype([](git_signature *aSignature) { git_signature_free(aSignature) GitSignaturePtr
A unique pointer for git_signature objects with automatic cleanup.
std::unique_ptr< git_repository, decltype([](git_repository *aRepo) { git_repository_free(aRepo) GitRepositoryPtr
A unique pointer for git_repository objects with automatic cleanup.
std::unique_ptr< git_config, decltype([](git_config *aConfig) { git_config_free(aConfig) GitConfigPtr
A unique pointer for git_config objects with automatic cleanup.
std::unique_ptr< git_reference, decltype([](git_reference *aRef) { git_reference_free(aRef) GitReferencePtr
A unique pointer for git_reference objects with automatic cleanup.
bool IsNetworkPath(const wxString &aPath)
Determines if a given path is a network shared file apth On Windows for example, any form of path is ...
bool IsFileHidden(const wxString &aFileName)
Helper function to determine the status of the 'Hidden' file attribute.
Definition: unix/io.cpp:70
bool StoreSecret(const wxString &aService, const wxString &aKey, const wxString &aSecret)
KICOMMON_API wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmapBundle &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
Definition: ui_common.cpp:376
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition: kicad_algo.h:100
PGM_BASE & Pgm()
The global program "get" accessor.
Definition: pgm_base.cpp:1071
#define NAMELESS_PROJECT
default name for nameless projects
Definition: project.h:43
std::vector< wxString > getProjects(const wxDir &dir)
static git_repository * get_git_repository_for_file(const char *filename)
static const wxChar * s_allowedExtensionsToList[]
static int git_create_branch(git_repository *aRepo, wxString &aBranchName)
wxDECLARE_EVENT(UPDATE_ICONS, wxCommandEvent)
The frame that shows the tree list of files and subdirectories inside the working directory.
#define wxFileSystemWatcher
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:403
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.