KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kigit_pcb_merge.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "kigit_pcb_merge.h"
22
26
27#include <board.h>
30#include <richio.h>
31#include <trace_helpers.h>
32
33#include <wx/log.h>
34
35void KIGIT_PCB_MERGE::findSetDifferences( const BOARD_ITEM_SET& aAncestorSet, const BOARD_ITEM_SET& aOtherSet,
36 std::vector<BOARD_ITEM*>& aAdded, std::vector<BOARD_ITEM*>& aRemoved,
37 std::vector<BOARD_ITEM*>& aChanged )
38{
39 auto it1 = aAncestorSet.begin();
40 auto it2 = aOtherSet.begin();
41
42 while( it1 != aAncestorSet.end() && it2 != aOtherSet.end() )
43 {
44 BOARD_ITEM* item1 = *it1;
45 BOARD_ITEM* item2 = *it2;
46
47 if( item1->m_Uuid < item2->m_Uuid )
48 {
49 aRemoved.push_back( item1 );
50 ++it1;
51 }
52 else if( item1->m_Uuid > item2->m_Uuid )
53 {
54 aAdded.push_back( item2 );
55 ++it2;
56 }
57 else
58 {
59 if( !( *item1 == *item2 ) )
60 aChanged.push_back( item1 );
61
62 ++it1;
63 ++it2;
64 }
65 }
66}
67
68
70{
72
73 const auto ancestor_set = aAncestor->GetItemSet();
74 const auto other_set = aOther->GetItemSet();
75
76 findSetDifferences( ancestor_set, other_set, differences.m_added, differences.m_removed, differences.m_changed );
77
78 return differences;
79}
80
81
83{
84 // The structural 3-way merge needs all three sides. A null ancestor entry
85 // means one side added a file that doesn't exist on the other; without a
86 // common ancestor there is nothing to diff against, so decline and let
87 // libgit2 fall back to its default handling. LoadMergeBlobs tolerates a
88 // missing ancestor, so this guard is kept explicit to preserve behavior.
89 if( !git_merge_driver_source_ancestor( m_mergeDriver ) )
90 {
91 wxLogTrace( traceGit, "PCB merge driver: no common ancestor, declining merge" );
92 return GIT_PASSTHROUGH;
93 }
94
96
97 if( int rc = KIGIT::LoadMergeBlobs( m_mergeDriver, blobs ); rc != 0 )
98 {
99 wxLogTrace( traceGit, "PCB merge driver: could not load merge blobs (rc=%d)", rc );
100 return rc;
101 }
102
103 // Parse each side from its decoded blob contents. LoadMergeBlobs has freed
104 // the underlying git_blob objects, so the boards are built from the strings.
105 STRING_LINE_READER ancestor_reader( blobs.ancestor, wxS( "ancestor" ) );
106 PCB_IO_KICAD_SEXPR_PARSER ancestor_parser( &ancestor_reader, nullptr, nullptr );
107 STRING_LINE_READER ours_reader( blobs.ours, wxS( "ours" ) );
108 PCB_IO_KICAD_SEXPR_PARSER ours_parser( &ours_reader, nullptr, nullptr );
109 STRING_LINE_READER theirs_reader( blobs.theirs, wxS( "theirs" ) );
110 PCB_IO_KICAD_SEXPR_PARSER theirs_parser( &theirs_reader, nullptr, nullptr );
111
112 std::unique_ptr<BOARD> ancestor_board;
113 std::unique_ptr<BOARD> ours_board;
114 std::unique_ptr<BOARD> theirs_board;
115
116 try
117 {
118 ancestor_board.reset( static_cast<BOARD*>( ancestor_parser.Parse() ) );
119 ours_board.reset( static_cast<BOARD*>( ours_parser.Parse() ) );
120 theirs_board.reset( static_cast<BOARD*>( theirs_parser.Parse() ) );
121 }
122 catch(const IO_ERROR& e)
123 {
124 wxLogTrace( traceGit, "Could not parse board: %s", e.What() );
125 return GIT_EUSER;
126 }
127 catch( ... )
128 {
129 wxLogTrace( traceGit, "Could not parse board: unknown error" );
130 return GIT_EUSER;
131 }
132
133 // Keep the legacy difference sets populated for any external consumer
134 // (none today, but the accessors are public API). The full 3-way merge
135 // logic runs through the new diff/merge engine.
136 KIGIT_PCB_MERGE_DIFFERENCES ancestor_ours_differences =
137 compareBoards( ancestor_board.get(), ours_board.get() );
138 KIGIT_PCB_MERGE_DIFFERENCES ancestor_theirs_differences =
139 compareBoards( ancestor_board.get(), theirs_board.get() );
140
141 std::set_intersection( ancestor_ours_differences.m_changed.begin(),
142 ancestor_ours_differences.m_changed.end(),
143 ancestor_theirs_differences.m_removed.begin(),
144 ancestor_theirs_differences.m_removed.end(),
145 std::inserter( we_modified_they_deleted, we_modified_they_deleted.begin() ) );
146 std::set_intersection( ancestor_theirs_differences.m_changed.begin(),
147 ancestor_theirs_differences.m_changed.end(),
148 ancestor_ours_differences.m_removed.begin(),
149 ancestor_ours_differences.m_removed.end(),
150 std::inserter( they_modified_we_deleted, they_modified_we_deleted.begin() ) );
151 std::set_intersection( ancestor_ours_differences.m_changed.begin(),
152 ancestor_ours_differences.m_changed.end(),
153 ancestor_theirs_differences.m_changed.begin(),
154 ancestor_theirs_differences.m_changed.end(),
155 std::inserter( both_modified, both_modified.begin() ) );
156
157 // Run the structured diff/merge pipeline so we can actually emit a
158 // merged blob into git's buffer. Project pointers from the parser
159 // belong to whichever PROJECT the loader supplied — for blob loads
160 // there is no project, so this is safe.
161 KICAD_DIFF::PCB_DIFFER ourDiffer( ancestor_board.get(), ours_board.get() );
162 KICAD_DIFF::PCB_DIFFER theirDiffer( ancestor_board.get(), theirs_board.get() );
163
165 KICAD_DIFF::MERGE_PLAN plan = engine.Plan( ourDiffer.Diff(), theirDiffer.Diff() );
166
167 // Captured before the plan is moved into the applier; drives the
168 // conflict-or-clean return below without a second Plan() recompute.
169 const bool planResolved = plan.Resolved();
170
171 KICAD_DIFF::PCB_MERGE_APPLIER applier( ancestor_board.get(), ours_board.get(),
172 theirs_board.get(), std::move( plan ) );
173 std::unique_ptr<BOARD> merged = applier.Apply();
174
175 if( !merged )
176 {
177 wxLogTrace( traceGit, "PCB merge applier failed to produce a board" );
178 return GIT_EUSER;
179 }
180
181 // Serialize the merged board into a std::string via PCB_IO_KICAD_SEXPR.
182 std::string serialized;
183
184 try
185 {
186 STRING_FORMATTER formatter;
187 PCB_IO_KICAD_SEXPR pcbIO;
188 pcbIO.SetOutputFormatter( &formatter );
189 pcbIO.Format( merged.get() );
190 serialized = formatter.GetString();
191 }
192 catch( const IO_ERROR& ioe )
193 {
194 wxLogTrace( traceGit, "Failed to serialize merged board: %s", ioe.What() );
195 return GIT_EUSER;
196 }
197
198 if( KIGIT::WriteToGitBuf( m_result, serialized ) != 0 )
199 {
200 wxLogTrace( traceGit, "Failed to allocate git_buf for merged board" );
201 return GIT_EUSER;
202 }
203
204 // The merged board is fully serialized above, so the working-tree file is
205 // always valid. If conflicts remained unresolved, tell libgit2 to record a
206 // conflict; the user resolves it via the mergetool, which recomputes the
207 // plan from ancestor/ours/theirs.
208 if( !planResolved )
209 return GIT_EMERGECONFLICT;
210
211 return 0;
212}
213
214
220int KIGIT_PCB_MERGE::Apply( const git_merge_driver_source* aSrc, const char** aPathOut,
221 unsigned int* aModeOut, git_buf* aMergedOut )
222{
223 return KIGIT::ApplyMergeDriver<KIGIT_PCB_MERGE>( aSrc, aPathOut, aModeOut, aMergedOut );
224}
std::set< BOARD_ITEM *, CompareByUuid > BOARD_ITEM_SET
Set of BOARD_ITEMs ordered by UUID.
Definition board.h:356
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const BOARD_ITEM_SET GetItemSet()
Collect every owned item (tracks, zones, generators, footprints, drawings, markers,...
Definition board.cpp:3894
const KIID m_Uuid
Definition eda_item.h:531
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Three-way merge plan generator.
MERGE_PLAN Plan(const DOCUMENT_DIFF &aAncestorOurs, const DOCUMENT_DIFF &aAncestorTheirs) const
Plan the merge given the canonical pair of diffs.
Diff two already-parsed BOARDs and produce a DOCUMENT_DIFF.
Definition pcb_differ.h:52
DOCUMENT_DIFF Diff() override
Produce a DOCUMENT_DIFF of the inputs the concrete differ was constructed with.
Materialize a MERGE_PLAN into a real merged BOARD.
std::unique_ptr< BOARD > Apply()
Produce the merged board.
std::set< BOARD_ITEM * > both_modified
void findSetDifferences(const BOARD_ITEM_SET &aAncestorSet, const BOARD_ITEM_SET &aOtherSet, std::vector< BOARD_ITEM * > &aAdded, std::vector< BOARD_ITEM * > &aRemoved, std::vector< BOARD_ITEM * > &aChanged)
git_merge_driver_source * m_mergeDriver
KIGIT_PCB_MERGE_DIFFERENCES compareBoards(BOARD *aAncestor, BOARD *aOther)
std::set< BOARD_ITEM * > they_modified_we_deleted
static int Apply(const git_merge_driver_source *aSrc, const char **aPathOut, unsigned int *aModeOut, git_buf *aMergedOut)
libgit2 merge-driver apply callback shim.
std::set< BOARD_ITEM * > we_modified_they_deleted
Read a Pcbnew s-expression formatted LINE_READER object and returns the appropriate BOARD_ITEM object...
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
void Format(const BOARD_ITEM *aItem) const
Output aItem to aFormatter in s-expression format.
void SetOutputFormatter(OUTPUTFORMATTER *aFormatter)
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:418
const std::string & GetString()
Definition richio.h:441
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:222
const wxChar *const traceGit
Flag to enable Git debugging output.
int WriteToGitBuf(git_buf *aBuf, const std::string &aContent)
Allocate a libgit2-owned buffer big enough for aContent and copy the bytes plus a trailing NUL.
int LoadMergeBlobs(const git_merge_driver_source *aSource, MERGE_BLOBS &aBlobs)
Look up the ancestor/ours/theirs blobs of a merge-driver source and decode them into aBlobs.
int ApplyMergeDriver(const git_merge_driver_source *aSrc, const char **aPathOut, unsigned int *aModeOut, git_buf *aMergedOut)
Shared libgit2 merge-driver apply callback shim.
Pcbnew s-expression file format parser definition.
Result of planning a 3-way merge.
Decoded ancestor/ours/theirs blob contents for a 3-way merge driver.
std::string ancestor
Empty when there is no common ancestor.
std::vector< BOARD_ITEM * > m_removed
std::vector< BOARD_ITEM * > m_changed
std::vector< BOARD_ITEM * > m_added
wxLogTrace helper definitions.