mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
349 lines
12 KiB
C++
349 lines
12 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 3
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/gpl-3.0.html
|
|
* or you may search the http://www.gnu.org website for the version 3 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "git_pull_handler.h"
|
|
#include <git/kicad_git_common.h>
|
|
|
|
#include <wx/log.h>
|
|
|
|
#include <iostream>
|
|
#include <time.h>
|
|
|
|
GIT_PULL_HANDLER::GIT_PULL_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo )
|
|
{}
|
|
|
|
GIT_PULL_HANDLER::~GIT_PULL_HANDLER()
|
|
{}
|
|
|
|
|
|
bool GIT_PULL_HANDLER::PerformFetch()
|
|
{
|
|
// Fetch updates from remote repository
|
|
git_remote* remote = nullptr;
|
|
|
|
if( git_remote_lookup( &remote, m_repo, "origin" ) != 0 )
|
|
{
|
|
AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ) );
|
|
return false;
|
|
}
|
|
|
|
git_remote_callbacks remoteCallbacks;
|
|
git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
|
|
remoteCallbacks.sideband_progress = progress_cb;
|
|
remoteCallbacks.transfer_progress = transfer_progress_cb;
|
|
remoteCallbacks.credentials = credentials_cb;
|
|
remoteCallbacks.payload = this;
|
|
|
|
if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
|
|
{
|
|
git_remote_free( remote );
|
|
AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin",
|
|
git_error_last()->message ) );
|
|
return false;
|
|
}
|
|
|
|
git_fetch_options fetchOptions;
|
|
git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
|
|
fetchOptions.callbacks = remoteCallbacks;
|
|
|
|
if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
|
|
{
|
|
git_remote_free( remote );
|
|
AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ),
|
|
"origin", git_error_last()->message ) );
|
|
return false;
|
|
}
|
|
|
|
git_remote_free( remote );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
PullResult GIT_PULL_HANDLER::PerformPull()
|
|
{
|
|
PullResult result = PullResult::Success;
|
|
|
|
if( !PerformFetch() )
|
|
return PullResult::Error;
|
|
|
|
git_oid pull_merge_oid = {};
|
|
|
|
if( git_repository_fetchhead_foreach( m_repo, fetchhead_foreach_cb, &pull_merge_oid ) )
|
|
{
|
|
AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
git_annotated_commit* fetchhead_commit;
|
|
|
|
if( git_annotated_commit_lookup( &fetchhead_commit, m_repo, &pull_merge_oid ) )
|
|
{
|
|
AddErrorString( _( "Could not lookup commit" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
const git_annotated_commit* merge_commits[] = { fetchhead_commit };
|
|
git_merge_analysis_t merge_analysis;
|
|
git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
|
|
|
|
if( git_merge_analysis( &merge_analysis, &merge_preference, m_repo, merge_commits, 1 ) )
|
|
{
|
|
AddErrorString( _( "Could not analyze merge" ) );
|
|
git_annotated_commit_free( fetchhead_commit );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
if( !( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL ) )
|
|
git_annotated_commit_free( fetchhead_commit );
|
|
|
|
if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
|
|
{
|
|
AddErrorString( _( "Invalid HEAD. Cannot merge." ) );
|
|
return PullResult::MergeFailed;
|
|
}
|
|
|
|
// Nothing to do if the repository is up to date
|
|
if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
|
|
{
|
|
git_repository_state_cleanup( m_repo );
|
|
return PullResult::UpToDate;
|
|
}
|
|
|
|
// Fast-forward is easy, just update the local reference
|
|
if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
|
|
{
|
|
return handleFastForward();
|
|
}
|
|
|
|
if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
|
|
{
|
|
PullResult ret = handleMerge( merge_commits, 1 );
|
|
git_annotated_commit_free( fetchhead_commit );
|
|
return ret;
|
|
}
|
|
|
|
//TODO: handle merges when they need to be resolved
|
|
|
|
return result;
|
|
}
|
|
|
|
const std::vector<std::pair<std::string, std::vector<CommitDetails>>>& GIT_PULL_HANDLER::GetFetchResults() const {
|
|
return m_fetchResults;
|
|
}
|
|
|
|
std::string GIT_PULL_HANDLER::getFirstLineFromCommitMessage( const std::string& aMessage )
|
|
{
|
|
size_t firstLineEnd = aMessage.find_first_of( '\n' );
|
|
|
|
if( firstLineEnd != std::string::npos )
|
|
return aMessage.substr( 0, firstLineEnd );
|
|
|
|
return aMessage;
|
|
}
|
|
|
|
std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime )
|
|
{
|
|
char dateBuffer[64];
|
|
time_t time = static_cast<time_t>( aTime.time );
|
|
strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", gmtime( &time ) );
|
|
return dateBuffer;
|
|
}
|
|
|
|
|
|
PullResult GIT_PULL_HANDLER::handleFastForward()
|
|
{
|
|
// Update local references with fetched data
|
|
git_reference* updatedRef = nullptr;
|
|
|
|
if( git_repository_head( &updatedRef, m_repo ) )
|
|
{
|
|
AddErrorString( _( "Could not get repository head" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
const char* updatedRefName = git_reference_name( updatedRef );
|
|
git_reference_free( updatedRef );
|
|
|
|
git_oid updatedRefOid;
|
|
if( git_reference_name_to_id( &updatedRefOid, m_repo, updatedRefName ) )
|
|
{
|
|
AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ), updatedRefName ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
git_checkout_options checkoutOptions;
|
|
git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
|
|
checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
|
|
if( git_checkout_head( m_repo, &checkoutOptions ) )
|
|
{
|
|
AddErrorString( _( "Failed to perform checkout operation." ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
// Collect commit details for updated references
|
|
git_revwalk* revWalker = nullptr;
|
|
git_revwalk_new( &revWalker, m_repo );
|
|
git_revwalk_sorting( revWalker, GIT_SORT_TIME );
|
|
git_revwalk_push_glob( revWalker, updatedRefName );
|
|
|
|
git_oid commitOid;
|
|
while( git_revwalk_next( &commitOid, revWalker ) == 0 )
|
|
{
|
|
git_commit* commit = nullptr;
|
|
|
|
if( git_commit_lookup( &commit, m_repo, &commitOid ) )
|
|
{
|
|
AddErrorString( wxString::Format( _( "Could not lookup commit '{}'" ), git_oid_tostr_s( &commitOid ) ) );
|
|
git_revwalk_free( revWalker );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
CommitDetails details;
|
|
details.m_sha = git_oid_tostr_s( &commitOid );
|
|
details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
|
|
details.m_author = git_commit_author( commit )->name;
|
|
details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
|
|
|
|
std::pair<std::string, std::vector<CommitDetails>>& branchCommits =
|
|
m_fetchResults.emplace_back();
|
|
branchCommits.first = updatedRefName;
|
|
branchCommits.second.push_back( details );
|
|
|
|
//TODO: log these to the parent
|
|
git_commit_free( commit );
|
|
}
|
|
|
|
git_revwalk_free( revWalker );
|
|
|
|
git_repository_state_cleanup( m_repo );
|
|
return PullResult::FastForward;
|
|
}
|
|
|
|
|
|
PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHeads,
|
|
size_t aMergeHeadsCount )
|
|
{
|
|
git_merge_options merge_opts;
|
|
git_merge_options_init( &merge_opts, GIT_MERGE_OPTIONS_VERSION );
|
|
|
|
git_checkout_options checkout_opts;
|
|
git_checkout_init_options( &checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION );
|
|
|
|
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
|
|
|
if( git_merge( m_repo, aMergeHeads, aMergeHeadsCount, &merge_opts, &checkout_opts ) )
|
|
{
|
|
AddErrorString( _( "Could not merge commits" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
// Get the repository index
|
|
git_index* index = nullptr;
|
|
if( git_repository_index( &index, m_repo ) )
|
|
{
|
|
AddErrorString( _( "Could not get repository index" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
// Check for conflicts
|
|
git_index_conflict_iterator* conflicts = nullptr;
|
|
if( git_index_conflict_iterator_new( &conflicts, index ) )
|
|
{
|
|
AddErrorString( _( "Could not get conflict iterator" ) );
|
|
return PullResult::Error;
|
|
}
|
|
|
|
const git_index_entry* ancestor = nullptr;
|
|
const git_index_entry* our = nullptr;
|
|
const git_index_entry* their = nullptr;
|
|
std::vector<ConflictData> conflict_data;
|
|
|
|
while( git_index_conflict_next( &ancestor, &our, &their, conflicts ) == 0 )
|
|
{
|
|
// Case 3: Both files have changed
|
|
if( ancestor && our && their )
|
|
{
|
|
ConflictData conflict_datum;
|
|
conflict_datum.filename = our->path;
|
|
conflict_datum.our_oid = our->id;
|
|
conflict_datum.their_oid = their->id;
|
|
conflict_datum.our_commit_time = our->mtime.seconds;
|
|
conflict_datum.their_commit_time = their->mtime.seconds;
|
|
conflict_datum.our_status = _( "Changed" );
|
|
conflict_datum.their_status = _( "Changed" );
|
|
conflict_datum.use_ours = true;
|
|
|
|
conflict_data.push_back( conflict_datum );
|
|
}
|
|
// Case 4: File added in both ours and theirs
|
|
else if( !ancestor && our && their )
|
|
{
|
|
ConflictData conflict_datum;
|
|
conflict_datum.filename = our->path;
|
|
conflict_datum.our_oid = our->id;
|
|
conflict_datum.their_oid = their->id;
|
|
conflict_datum.our_commit_time = our->mtime.seconds;
|
|
conflict_datum.their_commit_time = their->mtime.seconds;
|
|
conflict_datum.our_status = _( "Added" );
|
|
conflict_datum.their_status = _( "Added" );
|
|
conflict_datum.use_ours = true;
|
|
|
|
conflict_data.push_back( conflict_datum );
|
|
}
|
|
// Case 1: Remote file has changed or been added, local file has not
|
|
else if( their && !our )
|
|
{
|
|
// Accept their changes
|
|
git_index_add( index, their );
|
|
}
|
|
// Case 2: Local file has changed or been added, remote file has not
|
|
else if( our && !their )
|
|
{
|
|
// Accept our changes
|
|
git_index_add( index, our );
|
|
}
|
|
else
|
|
{
|
|
wxLogError( wxS( "Unexpected conflict state" ) );
|
|
}
|
|
}
|
|
|
|
if( conflict_data.empty() )
|
|
{
|
|
git_index_conflict_cleanup( index );
|
|
git_index_write( index );
|
|
}
|
|
|
|
git_index_conflict_iterator_free( conflicts );
|
|
git_index_free( index );
|
|
|
|
return conflict_data.empty() ? PullResult::Success : PullResult::MergeFailed;
|
|
}
|
|
|
|
|
|
void GIT_PULL_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
|
|
{
|
|
ReportProgress( aCurrent, aTotal, aMessage );
|
|
}
|