KiCad PCB EDA Suite
Loading...
Searching...
No Matches
command_git_mergedriver.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <cli/exit_codes.h>
28#include <string_utils.h>
30
31#include <wx/crt.h>
32#include <wx/filename.h>
33#include <wx/stdpaths.h>
34
35
36#define ARG_KIND "--kind"
37#define ARG_ANCESTOR "ancestor"
38#define ARG_OURS "ours"
39#define ARG_THEIRS "theirs"
40#define ARG_OUTPUT_F "--output"
41
42
44 COMMAND( "git-mergedriver" )
45{
46 // Invoked by git, not by users: keep it out of `kicad-cli --help`. The
47 // argparse stream operator skips suppressed subparsers while still routing
48 // and parsing them, so no help-rendering code is needed.
49 m_argParser.set_suppress( true );
50
51 m_argParser.add_description(
52 UTF8STDSTR( _( "Internal git merge-driver hook (configured automatically by KiCad's "
53 "git integration; not intended for direct use)." ) ) );
54
55 m_argParser.add_argument( ARG_KIND )
56 .required()
57 .help( UTF8STDSTR( _( "Document kind: pcb, sch, sym, or fp" ) ) )
58 .metavar( "KIND" );
59
60 m_argParser.add_argument( ARG_ANCESTOR ).metavar( "ANCESTOR" );
61 m_argParser.add_argument( ARG_OURS ).metavar( "OURS" );
62 m_argParser.add_argument( ARG_THEIRS ).metavar( "THEIRS" );
63
64 m_argParser.add_argument( "-o", ARG_OUTPUT_F ).required().metavar( "PATH" );
65}
66
67
69{
70 const std::string kindStr = m_argParser.get<std::string>( ARG_KIND );
71 const wxString ancestor = From_UTF8( m_argParser.get<std::string>( ARG_ANCESTOR ).c_str() );
72 const wxString ours = From_UTF8( m_argParser.get<std::string>( ARG_OURS ).c_str() );
73 const wxString theirs = From_UTF8( m_argParser.get<std::string>( ARG_THEIRS ).c_str() );
74 const wxString output = From_UTF8( m_argParser.get<std::string>( ARG_OUTPUT_F ).c_str() );
75
77 bool singleFile = false;
78 wxString ext;
79
80 if( kindStr == "pcb" )
81 {
84 }
85 else if( kindStr == "sch" )
86 {
89 }
90 else if( kindStr == "sym" )
91 {
94 }
95 else if( kindStr == "fp" )
96 {
97 // Git invokes the footprint driver per-file on extension-less temp
98 // blobs, so it is always the single-.kicad_mod form.
100 singleFile = true;
102 }
103 else
104 {
105 wxFprintf( stderr, _( "Unknown --kind '%s' (expected pcb, sch, sym, or fp)\n" ),
106 wxString::FromUTF8( kindStr ) );
108 }
109
110 // Git hands us extension-less temp paths (e.g. `.merge_file_XXX`), but the
111 // KiCad loaders pick their IO plugin by extension. Stage the three inputs
112 // into a temp directory under the correct extension so they load, then copy
113 // the merged result back to git's output path (%A).
114 const wxString tmpDir = wxFileName::CreateTempFileName( wxStandardPaths::Get().GetTempDir()
115 + wxFileName::GetPathSeparator()
116 + wxS( "kicad_mergedriver_" ) );
117 wxRemoveFile( tmpDir ); // CreateTempFileName makes a file; we want a dir at that name
118
119 if( !wxFileName::Mkdir( tmpDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
120 {
121 wxFprintf( stderr, _( "Could not create a temporary directory for the merge.\n" ) );
123 }
124
125 auto staged = [&]( const wxString& aName )
126 {
127 return tmpDir + wxFileName::GetPathSeparator() + aName + wxS( "." ) + ext;
128 };
129
130 const wxString stagedAncestor = staged( wxS( "ancestor" ) );
131 const wxString stagedOurs = staged( wxS( "ours" ) );
132 const wxString stagedTheirs = staged( wxS( "theirs" ) );
133 const wxString stagedOut = staged( wxS( "merged" ) );
134
135 bool copied = wxCopyFile( ancestor, stagedAncestor ) && wxCopyFile( ours, stagedOurs )
136 && wxCopyFile( theirs, stagedTheirs );
137
138 if( !copied )
139 {
140 wxFileName::Rmdir( tmpDir, wxPATH_RMDIR_RECURSIVE );
141 wxFprintf( stderr, _( "Could not stage merge inputs.\n" ) );
143 }
144
145 // Batch merge: interactive=false. Returns 0 on a clean merge (git records
146 // it) or non-zero when conflicts remain (git leaves the path unmerged for
147 // `git mergetool`). Either way a valid file is written to stagedOut.
148 int rc = KICAD_DIFF::DispatchMerge( aKiway, kind, stagedAncestor, stagedOurs, stagedTheirs,
149 stagedOut, /* interactive */ false, singleFile, nullptr );
150
151 // Copy the merged result back to %A whenever one was produced (clean merge
152 // or recorded conflict). On a hard load/parse error nothing usable exists,
153 // so leave git's output untouched.
155 && wxFileExists( stagedOut ) )
156 {
157 if( !wxCopyFile( stagedOut, output ) )
158 {
159 wxFileName::Rmdir( tmpDir, wxPATH_RMDIR_RECURSIVE );
160 wxFprintf( stderr, _( "Could not write merged output.\n" ) );
162 }
163 }
164
165 wxFileName::Rmdir( tmpDir, wxPATH_RMDIR_RECURSIVE );
166 return rc;
167}
argparse::ArgumentParser m_argParser
Definition command.h:113
COMMAND(const std::string &aName)
Define a new COMMAND instance.
Definition command.cpp:30
int doPerform(KIWAY &aKiway) override
The internal handler that should be overloaded to implement command specific processing and work.
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:311
#define UTF8STDSTR(s)
Definition command.h:27
#define ARG_KIND
#define ARG_ANCESTOR
#define ARG_THEIRS
#define ARG_OUTPUT_F
#define ARG_OURS
#define _(s)
static const std::string KiCadSchematicFileExtension
static const std::string KiCadSymbolLibFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string KiCadPcbFileExtension
static const int ERR_ARGS
Definition exit_codes.h:31
static const int ERR_RC_VIOLATIONS
Rules check violation count was greater than 0.
Definition exit_codes.h:37
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_INVALID_OUTPUT_CONFLICT
Definition exit_codes.h:34
int DispatchMerge(KIWAY &aKiway, DOC_KIND aKind, const wxString &aAncestor, const wxString &aOurs, const wxString &aTheirs, const wxString &aOutput, bool aInteractive, bool aSingleFile, REPORTER *aReporter)
Run a 3-way document/library merge by calling the owning kiface's KIFACE_MERGE_DOCUMENT function expo...
DOC_KIND
Document type a diff/merge entry point should route to, derived from a file path's extension.
wxString From_UTF8(const char *cstring)
bool copied
nlohmann::json output
Definition of file extensions used in Kicad.