KiCad PCB EDA Suite
Loading...
Searching...
No Matches
git_pull_handler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "git_pull_handler.h"
26
27#include <wx/log.h>
28
29#include <iostream>
30#include <time.h>
31
33{}
34
35
37{}
38
39
41{
42 // Fetch updates from remote repository
43 git_remote* remote = nullptr;
44
45 if( git_remote_lookup( &remote, m_repo, "origin" ) != 0 )
46 {
47 AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ) );
48 return false;
49 }
50
51 git_remote_callbacks remoteCallbacks;
52 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
53 remoteCallbacks.sideband_progress = progress_cb;
54 remoteCallbacks.transfer_progress = transfer_progress_cb;
55 remoteCallbacks.credentials = credentials_cb;
56 remoteCallbacks.payload = this;
57
58 m_testedTypes = 0;
60
61 if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
62 {
63 git_remote_free( remote );
64 AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin",
65 git_error_last()->message ) );
66 return false;
67 }
68
69 git_fetch_options fetchOptions;
70 git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
71 fetchOptions.callbacks = remoteCallbacks;
72
73 if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
74 {
75 git_remote_free( remote );
76 AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ),
77 "origin", git_error_last()->message ) );
78 return false;
79 }
80
81 git_remote_free( remote );
82
83 return true;
84}
85
86
88{
89 PullResult result = PullResult::Success;
90
91 if( !PerformFetch() )
92 return PullResult::Error;
93
94 git_oid pull_merge_oid = {};
95
96 if( git_repository_fetchhead_foreach( m_repo, fetchhead_foreach_cb, &pull_merge_oid ) )
97 {
98 AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
99 return PullResult::Error;
100 }
101
102 git_annotated_commit* fetchhead_commit;
103
104 if( git_annotated_commit_lookup( &fetchhead_commit, m_repo, &pull_merge_oid ) )
105 {
106 AddErrorString( _( "Could not lookup commit" ) );
107 return PullResult::Error;
108 }
109
110 const git_annotated_commit* merge_commits[] = { fetchhead_commit };
111 git_merge_analysis_t merge_analysis;
112 git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
113
114 if( git_merge_analysis( &merge_analysis, &merge_preference, m_repo, merge_commits, 1 ) )
115 {
116 AddErrorString( _( "Could not analyze merge" ) );
117 git_annotated_commit_free( fetchhead_commit );
118 return PullResult::Error;
119 }
120
121 if( !( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL ) )
122 git_annotated_commit_free( fetchhead_commit );
123
124 if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
125 {
126 AddErrorString( _( "Invalid HEAD. Cannot merge." ) );
127 return PullResult::MergeFailed;
128 }
129
130 // Nothing to do if the repository is up to date
131 if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
132 {
133 git_repository_state_cleanup( m_repo );
134 return PullResult::UpToDate;
135 }
136
137 // Fast-forward is easy, just update the local reference
138 if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
139 {
140 return handleFastForward();
141 }
142
143 if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
144 {
145 PullResult ret = handleMerge( merge_commits, 1 );
146 git_annotated_commit_free( fetchhead_commit );
147 return ret;
148 }
149
150 //TODO: handle merges when they need to be resolved
151
152 return result;
153}
154
155const std::vector<std::pair<std::string, std::vector<CommitDetails>>>&
157{
158 return m_fetchResults;
159}
160
161
162std::string GIT_PULL_HANDLER::getFirstLineFromCommitMessage( const std::string& aMessage )
163{
164 size_t firstLineEnd = aMessage.find_first_of( '\n' );
165
166 if( firstLineEnd != std::string::npos )
167 return aMessage.substr( 0, firstLineEnd );
168
169 return aMessage;
170}
171
172
173std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime )
174{
175 char dateBuffer[64];
176 time_t time = static_cast<time_t>( aTime.time );
177 strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", gmtime( &time ) );
178 return dateBuffer;
179}
180
181
183{
184 // Update local references with fetched data
185 auto git_reference_deleter =
186 []( void* p )
187 {
188 git_reference_free( static_cast<git_reference*>( p ) );
189 };
190
191 git_reference* rawRef = nullptr;
192
193 if( git_repository_head( &rawRef, m_repo ) )
194 {
195 AddErrorString( _( "Could not get repository head" ) );
196 return PullResult::Error;
197 }
198
199 // Defer destruction of the git_reference until this function exits somewhere, since
200 // updatedRefName etc. just point to memory inside the reference
201 std::unique_ptr<git_reference, decltype( git_reference_deleter )> updatedRef( rawRef );
202
203 const char* updatedRefName = git_reference_name( updatedRef.get() );
204
205 git_oid updatedRefOid;
206
207 if( git_reference_name_to_id( &updatedRefOid, m_repo, updatedRefName ) )
208 {
209 AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ),
210 updatedRefName ) );
211 return PullResult::Error;
212 }
213
214 git_checkout_options checkoutOptions;
215 git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
216 checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
217
218 if( git_checkout_head( m_repo, &checkoutOptions ) )
219 {
220 AddErrorString( _( "Failed to perform checkout operation." ) );
221 return PullResult::Error;
222 }
223
224 // Collect commit details for updated references
225 git_revwalk* revWalker = nullptr;
226 git_revwalk_new( &revWalker, m_repo );
227 git_revwalk_sorting( revWalker, GIT_SORT_TIME );
228 git_revwalk_push_glob( revWalker, updatedRefName );
229
230 git_oid commitOid;
231
232 while( git_revwalk_next( &commitOid, revWalker ) == 0 )
233 {
234 git_commit* commit = nullptr;
235
236 if( git_commit_lookup( &commit, m_repo, &commitOid ) )
237 {
238 AddErrorString( wxString::Format( _( "Could not lookup commit '{}'" ),
239 git_oid_tostr_s( &commitOid ) ) );
240 git_revwalk_free( revWalker );
241 return PullResult::Error;
242 }
243
244 CommitDetails details;
245 details.m_sha = git_oid_tostr_s( &commitOid );
246 details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
247 details.m_author = git_commit_author( commit )->name;
248 details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
249
250 std::pair<std::string, std::vector<CommitDetails>>& branchCommits =
251 m_fetchResults.emplace_back();
252 branchCommits.first = updatedRefName;
253 branchCommits.second.push_back( details );
254
255 //TODO: log these to the parent
256 git_commit_free( commit );
257 }
258
259 git_revwalk_free( revWalker );
260
261 git_repository_state_cleanup( m_repo );
262 return PullResult::FastForward;
263}
264
265
266PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHeads,
267 size_t aMergeHeadsCount )
268{
269 git_merge_options merge_opts;
270 git_merge_options_init( &merge_opts, GIT_MERGE_OPTIONS_VERSION );
271
272 git_checkout_options checkout_opts;
273 git_checkout_init_options( &checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION );
274
275 checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
276
277 if( git_merge( m_repo, aMergeHeads, aMergeHeadsCount, &merge_opts, &checkout_opts ) )
278 {
279 AddErrorString( _( "Could not merge commits" ) );
280 return PullResult::Error;
281 }
282
283 // Get the repository index
284 git_index* index = nullptr;
285
286 if( git_repository_index( &index, m_repo ) )
287 {
288 AddErrorString( _( "Could not get repository index" ) );
289 return PullResult::Error;
290 }
291
292 // Check for conflicts
293 git_index_conflict_iterator* conflicts = nullptr;
294
295 if( git_index_conflict_iterator_new( &conflicts, index ) )
296 {
297 AddErrorString( _( "Could not get conflict iterator" ) );
298 return PullResult::Error;
299 }
300
301 const git_index_entry* ancestor = nullptr;
302 const git_index_entry* our = nullptr;
303 const git_index_entry* their = nullptr;
304 std::vector<ConflictData> conflict_data;
305
306 while( git_index_conflict_next( &ancestor, &our, &their, conflicts ) == 0 )
307 {
308 // Case 3: Both files have changed
309 if( ancestor && our && their )
310 {
311 ConflictData conflict_datum;
312 conflict_datum.filename = our->path;
313 conflict_datum.our_oid = our->id;
314 conflict_datum.their_oid = their->id;
315 conflict_datum.our_commit_time = our->mtime.seconds;
316 conflict_datum.their_commit_time = their->mtime.seconds;
317 conflict_datum.our_status = _( "Changed" );
318 conflict_datum.their_status = _( "Changed" );
319 conflict_datum.use_ours = true;
320
321 conflict_data.push_back( conflict_datum );
322 }
323 // Case 4: File added in both ours and theirs
324 else if( !ancestor && our && their )
325 {
326 ConflictData conflict_datum;
327 conflict_datum.filename = our->path;
328 conflict_datum.our_oid = our->id;
329 conflict_datum.their_oid = their->id;
330 conflict_datum.our_commit_time = our->mtime.seconds;
331 conflict_datum.their_commit_time = their->mtime.seconds;
332 conflict_datum.our_status = _( "Added" );
333 conflict_datum.their_status = _( "Added" );
334 conflict_datum.use_ours = true;
335
336 conflict_data.push_back( conflict_datum );
337 }
338 // Case 1: Remote file has changed or been added, local file has not
339 else if( their && !our )
340 {
341 // Accept their changes
342 git_index_add( index, their );
343 }
344 // Case 2: Local file has changed or been added, remote file has not
345 else if( our && !their )
346 {
347 // Accept our changes
348 git_index_add( index, our );
349 }
350 else
351 {
352 wxLogError( wxS( "Unexpected conflict state" ) );
353 }
354 }
355
356 if( conflict_data.empty() )
357 {
358 git_index_conflict_cleanup( index );
359 git_index_write( index );
360 }
361
362 git_index_conflict_iterator_free( conflicts );
363 git_index_free( index );
364
365 return conflict_data.empty() ? PullResult::Success : PullResult::MergeFailed;
366}
367
368
369void GIT_PULL_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
370{
371 ReportProgress( aCurrent, aTotal, aMessage );
372}
void ReportProgress(int aCurrent, int aTotal, const wxString &aMessage)
Definition: git_progress.h:45
std::string getFirstLineFromCommitMessage(const std::string &aMessage)
PullResult handleFastForward()
PullResult handleMerge(const git_annotated_commit **aMergeHeads, size_t aMergeHeadsCount)
const std::vector< std::pair< std::string, std::vector< CommitDetails > > > & GetFetchResults() const
GIT_PULL_HANDLER(KIGIT_COMMON *aCommon)
void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage) override
std::string getFormattedCommitDate(const git_time &aTime)
std::vector< std::pair< std::string, std::vector< CommitDetails > > > m_fetchResults
PullResult PerformPull()
git_repository * m_repo
unsigned m_testedTypes
void AddErrorString(const wxString aErrorString)
#define _(s)
PullResult
int fetchhead_foreach_cb(const char *, const char *, const git_oid *aOID, unsigned int aIsMerge, void *aPayload)
int transfer_progress_cb(const git_transfer_progress *aStats, void *aPayload)
int credentials_cb(git_cred **aOut, const char *aUrl, const char *aUsername, unsigned int aAllowedTypes, void *aPayload)
int progress_cb(const char *str, int len, void *data)
std::string m_sha
std::string m_date
std::string m_firstLine
std::string m_author
std::string their_status
std::string our_status
std::string filename
git_time_t their_commit_time
git_time_t our_commit_time