Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,494 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzToolsFramework/SourceControl/LocalFileSCComponent.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/StringFunc/StringFunc.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
#include <QDir>
#include <QDirIterator>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
void RefreshInfoFromFileSystem(SourceControlFileInfo& fileInfo)
{
fileInfo.m_flags = 0;
if (!AZ::IO::SystemFile::Exists(fileInfo.m_filePath.c_str()))
{
fileInfo.m_flags |= SCF_Writeable; // Non-existent files are not read only
}
else
{
fileInfo.m_flags |= SCF_Tracked;
if (AZ::IO::SystemFile::IsWritable(fileInfo.m_filePath.c_str()))
{
fileInfo.m_flags |= SCF_Writeable | SCF_OpenByUser;
}
}
fileInfo.m_status = SCS_OpSuccess;
}
void RemoveReadOnly(const SourceControlFileInfo& fileInfo)
{
if (!fileInfo.HasFlag(SCF_Writeable) && fileInfo.HasFlag(SCF_Tracked))
{
AZ::IO::SystemFile::SetWritable(fileInfo.m_filePath.c_str(), true);
}
}
void LocalFileSCComponent::Activate()
{
SourceControlConnectionRequestBus::Handler::BusConnect();
SourceControlCommandBus::Handler::BusConnect();
}
void LocalFileSCComponent::Deactivate()
{
SourceControlCommandBus::Handler::BusDisconnect();
SourceControlConnectionRequestBus::Handler::BusDisconnect();
}
void LocalFileSCComponent::GetFileInfo(const char* fullFilePath, const SourceControlResponseCallback& respCallback)
{
SourceControlFileInfo fileInfo(fullFilePath);
auto job = AZ::CreateJobFunction([fileInfo, respCallback]() mutable
{
RefreshInfoFromFileSystem(fileInfo);
AZ::TickBus::QueueFunction(respCallback, fileInfo.CompareStatus(SCS_OpSuccess), fileInfo);
}, true);
job->Start();
}
void LocalFileSCComponent::GetBulkFileInfo(const AZStd::unordered_set<AZStd::string>& fullFilePaths, const SourceControlResponseCallbackBulk& respCallback)
{
auto job = AZ::CreateJobFunction([fullFilePaths, respCallback]() mutable
{
AZStd::vector<SourceControlFileInfo> fileInfo;
for (const AZStd::string& fullFilePath : fullFilePaths)
{
for (QString file : GetFiles(fullFilePath.c_str()))
{
fileInfo.push_back();
fileInfo.back().m_filePath = file.toUtf8().constData();
RefreshInfoFromFileSystem(fileInfo.back());
}
}
AZ::TickBus::QueueFunction(respCallback, true, fileInfo);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestEdit(const char* fullFilePath, bool /*allowMultiCheckout*/, const SourceControlResponseCallback& respCallback)
{
SourceControlFileInfo fileInfo(fullFilePath);
auto job = AZ::CreateJobFunction([fileInfo, respCallback]() mutable
{
RefreshInfoFromFileSystem(fileInfo);
RemoveReadOnly(fileInfo);
RefreshInfoFromFileSystem(fileInfo);
// As a quality of life improvement for our users, we want request edit
// to report success in the case where a file doesn't exist. We do this so
// developers can always call RequestEdit before a save operation; instead of:
// File Exists --> RequestEdit, then SaveOperation
// File Does not Exist --> SaveOperation, then RequestEdit
AZ::TickBus::QueueFunction(respCallback, fileInfo.HasFlag(SCF_Writeable), fileInfo);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestEditBulk(const AZStd::unordered_set<AZStd::string>& fullFilePaths, bool /*allowMultiCheckout*/, const SourceControlResponseCallbackBulk& respCallback)
{
auto job = AZ::CreateJobFunction([fullFilePaths, respCallback]() mutable
{
AZStd::vector<SourceControlFileInfo> info;
for (const auto& filePath : fullFilePaths)
{
info.push_back();
auto& fileInfo = info.back();
fileInfo.m_filePath = filePath;
RefreshInfoFromFileSystem(fileInfo);
RemoveReadOnly(fileInfo);
RefreshInfoFromFileSystem(fileInfo);
}
// As a quality of life improvement for our users, we want request edit
// to report success in the case where a file doesn't exist. We do this so
// developers can always call RequestEdit before a save operation; instead of:
// File Exists --> RequestEdit, then SaveOperation
// File Does not Exist --> SaveOperation, then RequestEdit
AZ::TickBus::QueueFunction(respCallback, true, info);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestDelete(const char* fullFilePath, const SourceControlResponseCallback& respCallback)
{
RequestDeleteExtended(fullFilePath, false, respCallback);
}
void LocalFileSCComponent::RequestDeleteExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallback& respCallback)
{
SourceControlFileInfo fileInfo(fullFilePath);
auto job = AZ::CreateJobFunction([fileInfo, skipReadOnly, respCallback]() mutable
{
RefreshInfoFromFileSystem(fileInfo);
if (!skipReadOnly)
{
RemoveReadOnly(fileInfo);
}
auto succeeded = AZ::IO::SystemFile::Delete(fileInfo.m_filePath.c_str());
RefreshInfoFromFileSystem(fileInfo);
AZ::TickBus::QueueFunction(respCallback, succeeded, fileInfo);
}, true);
job->Start();
}
bool LocalFileSCComponent::SplitWildcardPath(QString path, QString& root, QString& wildcardEntry, QString& remaining)
{
static constexpr char WildcardCharacter = '*';
static constexpr char RecursiveWildcard[] = "...";
int firstWildcardIndex = path.indexOf(WildcardCharacter);
if(firstWildcardIndex < 0)
{
firstWildcardIndex = path.indexOf(RecursiveWildcard);
}
if(firstWildcardIndex >= 0)
{
int lastSlashBeforeWildcard = path.lastIndexOf(AZ_CORRECT_FILESYSTEM_SEPARATOR, firstWildcardIndex);
root = path.left(lastSlashBeforeWildcard + 1); // Include the separator
wildcardEntry = path.mid(lastSlashBeforeWildcard + 1); // Skip the separator
remaining = "";
int nextSlashAfterWildcard = wildcardEntry.indexOf(AZ_CORRECT_FILESYSTEM_SEPARATOR);
if(nextSlashAfterWildcard >= 0)
{
remaining = wildcardEntry.mid(nextSlashAfterWildcard + 1); // Skip the separator
wildcardEntry = wildcardEntry.left(nextSlashAfterWildcard); // Skip the separator
}
return true;
}
return false;
}
QStringList RecurseAllFiles(QString path)
{
QDirIterator itr(path, QDir::Files | QDir::NoSymLinks | QDir::Hidden, QDirIterator::Subdirectories);
QStringList result;
while(itr.hasNext())
{
result += itr.next();
}
return result;
}
bool LocalFileSCComponent::ResolveOneWildcardLevel(QString search, QStringList& result)
{
QString root;
QString searchEntry;
QString remaining;
if(SplitWildcardPath(search, root, searchEntry, remaining))
{
bool recursive = searchEntry.endsWith("...");
searchEntry = searchEntry.replace("...", "*");
QDir searchRoot(root);
QStringList nameFilters;
nameFilters << searchEntry;
QDir::Filters dirFilters = remaining.isEmpty() ? QDir::Files : QDir::Dirs;
bool searchEverything = recursive && remaining.isEmpty();
QStringList files = searchRoot.entryList(nameFilters, dirFilters | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden);
for (QString& file : files)
{
file = root + file;
if (!remaining.isEmpty())
{
file += AZ_CORRECT_FILESYSTEM_SEPARATOR + remaining;
}
}
result += files;
if(searchEverything)
{
for (QString& file : searchRoot.entryList(nameFilters, QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden))
{
result += RecurseAllFiles(root + file);
}
}
return !remaining.isEmpty();
}
if(AZ::IO::SystemFile::Exists(search.toUtf8().constData()))
{
result = QStringList{ search };
}
else
{
result = QStringList{};
}
return !remaining.isEmpty();
}
QStringList LocalFileSCComponent::GetFiles(QString absolutePathQuery)
{
AZStd::string azPath = absolutePathQuery.toUtf8().constData();
AZ::StringFunc::Replace(azPath, AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
QString remaining;
QStringList entries;
if(!ResolveOneWildcardLevel(azPath.c_str(), entries))
{
return entries;
}
QStringList results;
for (QString entry : entries)
{
results += GetFiles(entry);
}
return results;
}
AZStd::string LocalFileSCComponent::ResolveWildcardDestination(AZStd::string_view absFile, AZStd::string_view absSearch, AZStd::string destination)
{
AZStd::string searchAsRegex = absSearch;
AZ::StringFunc::Replace(searchAsRegex, AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
AZ::StringFunc::Replace(destination, AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
AZ::StringFunc::Replace(searchAsRegex, "...", "*");
AZ::StringFunc::Replace(destination, "...", "*");
AZStd::regex specialCharacters(R"([\\.?^$+(){}[\]-])");
// Escape the regex special characters
searchAsRegex = AZStd::regex_replace(searchAsRegex, specialCharacters, R"(\$0)");
// Replace * with .*
searchAsRegex = AZStd::regex_replace(searchAsRegex, AZStd::regex(R"(\*)"), R"((.*))");
AZStd::smatch result;
// Match absSearch against absFile to find what each * expands to
if (AZStd::regex_search(absFile.begin(), absFile.end(), result, AZStd::regex(searchAsRegex, AZStd::regex::icase)))
{
// For each * expansion, replace the * in the destination with the expanded result
for (size_t i = 1; i < result.size(); ++i)
{
auto matchedString = result[i].str();
// Only the last match can match across directory levels
if (matchedString.find(AZ_CORRECT_FILESYSTEM_SEPARATOR) != matchedString.npos && i < result.size() - 1)
{
AZ_Error("LocalFileSCComponent", false, "Wildcard cannot match across directory levels. Please simplify your search or put a wildcard at the end of the search to match across directories.");
return {};
}
destination.replace(destination.find('*'), 1, result[i].str().c_str());
}
}
return destination;
}
void LocalFileSCComponent::RequestDeleteBulk(const char* fullFilePath, const SourceControlResponseCallbackBulk& respCallback)
{
RequestDeleteBulkExtended(fullFilePath, false, respCallback);
}
void LocalFileSCComponent::RequestDeleteBulkExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback)
{
AZStd::string fullFilePathString = fullFilePath; // We need a string we can copy to the job thread since fullFilePath could go out of scope before the job runs
auto job = AZ::CreateJobFunction([fullFilePathString, skipReadOnly, respCallback]() mutable
{
AZStd::vector<SourceControlFileInfo> info;
QStringList files = GetFiles(fullFilePathString.c_str());
for (QString file : files)
{
info.push_back();
auto& fileInfo = info.back();
fileInfo.m_filePath = file.toUtf8().constData();
RefreshInfoFromFileSystem(fileInfo);
if (!skipReadOnly)
{
RemoveReadOnly(fileInfo);
}
auto succeeded = AZ::IO::SystemFile::Delete(fileInfo.m_filePath.c_str());
RefreshInfoFromFileSystem(fileInfo);
fileInfo.m_status = succeeded ? SCS_OpSuccess : SCS_ProviderError;
}
AZ::TickBus::QueueFunction(respCallback, true, info);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestRevert(const char* fullFilePath, const SourceControlResponseCallback& respCallback)
{
// Get the info, and fail if the file doesn't exist.
GetFileInfo(fullFilePath, respCallback);
}
void LocalFileSCComponent::RequestLatest(const char* fullFilePath, const SourceControlResponseCallback& respCallback)
{
SourceControlFileInfo fileInfo(fullFilePath);
auto job = AZ::CreateJobFunction([fileInfo, respCallback]() mutable
{
RefreshInfoFromFileSystem(fileInfo);
AZ::TickBus::QueueFunction(respCallback, true, fileInfo);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestRename(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallback& respCallback)
{
RequestRenameExtended(sourcePathFull, destPathFull, false, respCallback);
}
void LocalFileSCComponent::RequestRenameExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallback& respCallback)
{
SourceControlFileInfo fileInfoSrc(sourcePathFull);
SourceControlFileInfo fileInfoDst(destPathFull);
auto job = AZ::CreateJobFunction([fileInfoSrc, fileInfoDst, skipReadOnly, respCallback]() mutable
{
bool succeeded = true;
RefreshInfoFromFileSystem(fileInfoSrc);
if (!skipReadOnly || fileInfoSrc.HasFlag(SourceControlFlags::SCF_Writeable))
{
succeeded = AZ::IO::SystemFile::Rename(fileInfoSrc.m_filePath.c_str(), fileInfoDst.m_filePath.c_str());
RefreshInfoFromFileSystem(fileInfoDst);
fileInfoDst.m_status = succeeded ? SCS_OpSuccess : SCS_ProviderError;
}
else
{
fileInfoDst.m_status = SCS_ProviderError;
}
AZ::TickBus::QueueFunction(respCallback, succeeded, fileInfoDst);
}, true);
job->Start();
}
void LocalFileSCComponent::RequestRenameBulk(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallbackBulk& respCallback)
{
RequestRenameBulkExtended(sourcePathFull, destPathFull, false, respCallback);
}
void LocalFileSCComponent::RequestRenameBulkExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback)
{
AZStd::string sourcePathFullString = sourcePathFull;
AZStd::string destPathFullString = destPathFull;
auto job = AZ::CreateJobFunction([sourcePathFullString, destPathFullString, skipReadOnly, respCallback]() mutable
{
bool success = true;
AZStd::vector<SourceControlFileInfo> info;
AZ::s64 sourceWildcardCount = std::count(sourcePathFullString.begin(), sourcePathFullString.end(), '*');
AZ::s64 destinationWildcardCount = std::count(destPathFullString.begin(), destPathFullString.end(), '*');
if (sourceWildcardCount != destinationWildcardCount)
{
success = false;
AZ_Error("LocalFileSCComponent", false, "Source and destination paths must have the same number of wildcards.");
}
else
{
QStringList files = GetFiles(sourcePathFullString.c_str());
for (QString file : files)
{
AZStd::string absFilePath(file.toUtf8().constData());
AZ::StringFunc::Replace(absFilePath, AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
auto destination = ResolveWildcardDestination(absFilePath, sourcePathFullString, destPathFullString);
SourceControlFileInfo fileInfoSrc(file.toUtf8().constData());
SourceControlFileInfo fileInfoDst(destination.c_str());
RefreshInfoFromFileSystem(fileInfoSrc);
bool succeeded = false;
if (!skipReadOnly || fileInfoSrc.HasFlag(SourceControlFlags::SCF_Writeable))
{
AZStd::string destinationFolder;
AZ::StringFunc::Path::GetFullPath(fileInfoDst.m_filePath.c_str(), destinationFolder);
AZ::IO::SystemFile::CreateDir(destinationFolder.c_str());
succeeded = AZ::IO::SystemFile::Rename(file.toUtf8().constData(), fileInfoDst.m_filePath.c_str());
RefreshInfoFromFileSystem(fileInfoDst);
}
fileInfoDst.m_status = succeeded ? SCS_OpSuccess : SCS_ProviderError;
info.push_back(fileInfoDst);
}
}
AZ::TickBus::QueueFunction(respCallback, success, info);
}, true);
job->Start();
}
void LocalFileSCComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<LocalFileSCComponent, AZ::Component>()
;
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,89 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <QString>
#include <QStringList>
namespace AzToolsFramework
{
class LocalFileSCComponent
: public AZ::Component
, private SourceControlCommandBus::Handler
, private SourceControlConnectionRequestBus::Handler
{
friend class PerforceComponent;
public:
AZ_COMPONENT(LocalFileSCComponent, "{5AE6565F-046D-42F4-8E95-77C163A98420}");
// Resolves an absolute wildcard path to a list of all matching files
// Note that wildcards will not match across a directory level. EX: C:\some*\file.txt will NOT match C:\something\folder\file.txt. It WILL match C:\something\file.txt
static QStringList GetFiles(QString absolutePathQuery);
//////////////////////////////////////////////////////////////////////////
// AZ::Component overrides
void Init() override {}
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
private:
static void Reflect(AZ::ReflectContext* context);
// Takes a path and splits it into 3 parts: Root folder, wildcard file/folder entry, and the remaining path
// EX: C:/some/new*folder/file.txt -> C:/some/, new*folder, file.txt
static bool SplitWildcardPath(QString path, QString& root, QString& wildcardEntry, QString& remaining);
// Takes a wildcard path and adds all entries that match the first wildcard entry to result
// EX: C:/some/new*folder/file*.txt -> { C:/some/new1folder/file*.txt, C:/some/new2folder/file*.txt, etc }
// Note that multiple wildcards at one directory level will be resolved at the same time (EX: C:/some*folder*name/file.txt will resolve in 1 pass)
// Returns true if there are more levels (wildcards) to resolve
static bool ResolveOneWildcardLevel(QString search, QStringList& result);
// Takes an absolute path to a file, the absolute wildcard path used to look up that file, and an absolute wildcard destination path to move that file to
// Returns the destination path with the wildcards resolved
static AZStd::string ResolveWildcardDestination(AZStd::string_view absFile, AZStd::string_view absSearch, AZStd::string destination);
//////////////////////////////////////////////////////////////////////////
// SourceControlCommandBus::Handler overrides
void GetFileInfo(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void GetBulkFileInfo(const AZStd::unordered_set<AZStd::string>& fullFilePaths, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestEdit(const char* fullFilePath, bool allowMultiCheckout, const SourceControlResponseCallback& respCallback) override;
void RequestEditBulk(const AZStd::unordered_set<AZStd::string>& fullFilePaths, bool allowMultiCheckout, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestDelete(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestDeleteExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallback& respCallback) override;
void RequestDeleteBulk(const char* fullFilePath, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestDeleteBulkExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestRevert(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestLatest(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestRename(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallback& respCallback) override;
void RequestRenameExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallback& respCallback) override;
void RequestRenameBulk(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestRenameBulkExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// SourceControlConnectionRequestBus::Handler overrides
void EnableSourceControl(bool) override {}
bool IsActive() const override { return false; }
void EnableTrust(bool, AZStd::string) override {}
void SetConnectionSetting(const char*, const char*, const SourceControlSettingCallback&) override {}
void GetConnectionSetting(const char*, const SourceControlSettingCallback&) override {}
//////////////////////////////////////////////////////////////////////////
};
} // namespace AzToolsFramework
@@ -0,0 +1,268 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/parallel/semaphore.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/SourceControl/LocalFileSCComponent.h>
namespace AzToolsFramework
{
class PerforceConnection;
class PerforceJobRequest
{
public:
enum RequestType
{
PJR_Invalid = 0,
PJR_Stat,
PJR_StatBulk,
PJR_Add,
PJR_Edit,
PJR_EditBulk,
PJR_Delete,
PJR_DeleteBulk,
PJR_Revert,
PJR_Rename,
PJR_RenameBulk,
PJR_Sync,
};
RequestType m_requestType { PJR_Invalid };
AZStd::string m_requestPath;
AZStd::string m_targetPath;
AZStd::unordered_set<AZStd::string> m_bulkFilePaths;
bool m_allowMultiCheckout{};
bool m_skipReadonly{};
SourceControlResponseCallback m_callback{ nullptr };
SourceControlResponseCallbackBulk m_bulkCallback{ nullptr };
PerforceJobRequest() = default;
PerforceJobRequest(RequestType requestType, const AZStd::string& requestPath, SourceControlResponseCallback responseCB)
: m_requestType(requestType)
, m_requestPath(requestPath)
, m_callback(AZStd::move(responseCB))
{}
PerforceJobRequest(RequestType requestType, const AZStd::string& requestPath, SourceControlResponseCallbackBulk responseCB)
: m_requestType(requestType)
, m_requestPath(requestPath)
, m_bulkCallback(AZStd::move(responseCB))
{}
PerforceJobRequest(RequestType requestType, const AZStd::unordered_set<AZStd::string>& bulkFilePaths, SourceControlResponseCallbackBulk responseCB)
: m_requestType(requestType)
, m_bulkFilePaths(bulkFilePaths)
, m_bulkCallback(AZStd::move(responseCB))
{}
};
class PerforceJobResult
{
public:
SourceControlResponseCallback m_callback{ nullptr };
SourceControlResponseCallbackBulk m_bulkCallback{ nullptr };
bool m_succeeded{};
SourceControlFileInfo m_fileInfo;
AZStd::vector<SourceControlFileInfo> m_bulkFileInfo;
PerforceJobResult() = default;
};
class PerforceSettingResult
{
public:
PerforceSettingResult() = default;
void UpdateSettingInfo(const AZStd::string& value);
SourceControlSettingCallback m_callback = nullptr;
SourceControlSettingInfo m_settingInfo;
};
typedef AZStd::unordered_map<AZStd::string, AZStd::string> PerforceMap;
// the perforce component's job is to manage perforce connectivity and execute perforce commands
// it parses the status of perforce commands and returns results.
// it has helpers to determine what needs to be done with a file in order to remove it or to add it.
// for example, if a file is checked out and needs to be deleted, it knows that it will need to both revert the file and mark it for delete
// in order to get to where we want it to be, from where we are now.
// it does not keep track of individual files or watch directories or anything
class PerforceComponent
: public AZ::Component
, private SourceControlCommandBus::Handler
, private SourceControlConnectionRequestBus::Handler
{
public:
AZ_COMPONENT(PerforceComponent, "{680C2C8B-37CA-42EB-9E50-06AB2474201E}")
AZStd::string m_autoChangelistDescription;
PerforceComponent() = default;
~PerforceComponent() override = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component overrides
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
protected:
void SetConnection(PerforceConnection* connection);
private:
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
//////////////////////////////////////////////////////////////////////////
// SourceControlCommandBus::Handler overrides
void GetFileInfo(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void GetBulkFileInfo(const AZStd::unordered_set<AZStd::string>& fullFilePaths, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestEdit(const char* fullFilePath, bool allowMultiCheckout, const SourceControlResponseCallback& respCallback) override;
void RequestEditBulk(const AZStd::unordered_set<AZStd::string>& fullFilePaths, bool allowMultiCheckout, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestDelete(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestDeleteExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallback& respCallback) override;
void RequestDeleteBulk(const char* fullFilePath, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestDeleteBulkExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestRevert(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestLatest(const char* fullFilePath, const SourceControlResponseCallback& respCallback) override;
void RequestRename(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallback& respCallback) override;
void RequestRenameExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallback& respCallback) override;
void RequestRenameBulk(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallbackBulk& respCallback) override;
void RequestRenameBulkExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// SourceControlConnectionRequestBus::Handler overrides
void EnableSourceControl(bool enable) override;
bool IsActive() const override { return m_connectionState == SourceControlState::Active; }
void EnableTrust(bool enable, AZStd::string fingerprint) override;
void SetConnectionSetting(const char* key, const char* value, const SourceControlSettingCallback& respCallBack) override;
void GetConnectionSetting(const char* key, const SourceControlSettingCallback& respCallBack) override;
SourceControlState GetSourceControlState() const override { return m_connectionState; }
//////////////////////////////////////////////////////////////////////////
SourceControlFileInfo GetFileInfo(const char*);
AZStd::vector<SourceControlFileInfo> GetBulkFileInfo(const char* requestPath) const;
AZStd::vector<SourceControlFileInfo> GetBulkFileInfo(const AZStd::unordered_set<AZStd::string>& requestPaths) const;
//! Attempt to checkout a file
//! - If the file is marked for add, nothing will occur, its already writable for you.
//! - If the file is marked for delete, it will be reverted, then checked out, so that its writable and ready to add.
//! - If the file is marked for checkout already, nothing will occur (but we'll return true)
//! - If the file is not in perforce, nothing will occur (but we'll return true).
//! Error conditions will occur (false returned) when someone else has the file checked out, the file is locked, or from insufficient permissions
//! Note that calling this function will 'claim' the file into the editor's changelist, even if its already open on another changelist.
//! Note You cant check a file out if you don't have latest.
bool RequestEdit(const char* fullFilePath, bool allowMultiCheckout);
bool RequestEditBulk(const AZStd::unordered_set<AZStd::string>& fullFilePath, bool allowMultiCheckout);
//! Attempt to delete a file from both perforce and local
bool RequestDelete(const char* fullFilePath);
//! Attempt to delete a file from both perforce and local
bool RequestDeleteBulk(const char* fullFilePath);
//! Attempt to get the latest revision of the file
bool RequestLatest(const char* fullFilePath);
//! Attempt to revert a file to its last changelist
bool RequestRevert(const char* fullFilePath);
//! Attempt to rename a file
bool RequestRename(const char* sourcePathFull, const char* destPathFull);
//! Attempt to rename a file
bool RequestRenameBulk(const char* sourcePathFull, const char* destPathFull);
bool ClaimChangedFile(const char* fullFilePath, int changelistTarget);
bool ExecuteAdd(const char* filePath);
bool ExecuteEdit(const char* filePath, bool allowMultiCheckout, bool allowAdd);
bool ExecuteEditBulk(const AZStd::unordered_set<AZStd::string>& filePaths, bool allowMultiCheckout, bool allowAdd);
bool ExecuteDelete(const char* filePath);
bool ExecuteDeleteBulk(const char* filePath);
bool ExecuteSync(const char* filePath);
bool ExecuteRevert(const char* filePath);
bool ExecuteMove(const char* sourcePath, const char* destPath);
bool ExecuteMoveBulk(const char* sourcePath, const char* destPath);
void QueueJobRequest(PerforceJobRequest&& jobRequest);
void QueueSettingResponse(const PerforceSettingResult& result);
bool CheckConnectivityForAction(const char* actionDesc, const char* filePath) const;
bool ExecuteAndParseFstat(const char* filePath, bool& sourceAwareFile);
bool ExecuteAndParseFstat(const char* filePath, AZStd::vector<PerforceMap>& commandMap) const;
bool ExecuteAndParseFstat(const AZStd::unordered_set<AZStd::string>& filePaths, AZStd::vector<PerforceMap>& commandMap) const;
bool ExecuteAndParseSet(const char* key, const char* value) const;
bool CommandSucceeded();
bool UpdateTrust();
bool IsTrustKeyValid() const;
void TestConnectionTrust(bool attemptResolve);
void VerifyP4PortIsSet();
bool IsConnectionValid() const;
bool CacheClientConfig() const;
void TestConnectionValid();
bool UpdateConnectivity();
void DropConnectivity();
int GetOrCreateOurChangelist();
int FindExistingChangelist();
void ThreadWorker();
AZStd::thread m_WorkerThread;
AZStd::semaphore m_WorkerSemaphore;
AZStd::queue<PerforceJobRequest> m_workerQueue;
AZStd::queue<PerforceJobResult> m_resultQueue;
AZStd::queue<PerforceSettingResult> m_settingsQueue;
AZStd::mutex m_WorkerQueueMutex;
AZStd::mutex m_ResultQueueMutex;
AZStd::mutex m_SettingsQueueMutex;
AZStd::atomic_bool m_shutdownThreadSignal;
AZStd::atomic_bool m_waitingOnTrust;
void ProcessJob(const PerforceJobRequest& request);
void ProcessJobOffline(const PerforceJobRequest& request);
void ProcessResultQueue();
AZStd::thread::id m_ProcessThreadID; // used for debugging!
bool ParseOutput(PerforceMap& perforceMap, AZStd::string& perforceOutput, const char* lineDelim = nullptr) const;
bool ParseDuplicateOutput(AZStd::vector<PerforceMap>& perforceMapList, AZStd::string& perforceOutput) const;
LocalFileSCComponent m_localFileSCComponent;
AZStd::atomic_bool m_offlineMode;
AZStd::atomic_bool m_resolveKey;
AZStd::atomic_bool m_trustedKey;
AZStd::atomic_bool m_testTrust;
AZStd::atomic_bool m_testConnection;
AZStd::atomic_bool m_validConnection;
SourceControlState m_connectionState;
};
} // namespace AzToolsFramework
@@ -0,0 +1,480 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
#include <AzToolsFramework/Process/ProcessWatcher.h>
#define SCC_WINDOW "Source Control"
namespace AzToolsFramework
{
namespace
{
char s_EndOfFileChar(26);
}
AZStd::string PerforceCommand::GetCurrentChangelistNumber(const PerforceMap* map) const
{
return GetOutputValue("change", map);
}
AZStd::string PerforceCommand::GetHaveRevision(const PerforceMap* map) const
{
return GetOutputValue("haveRev", map);
}
AZStd::string PerforceCommand::GetHeadRevision(const PerforceMap* map) const
{
return GetOutputValue("headRev", map);
}
AZStd::string PerforceCommand::GetOtherUserCheckedOut(const PerforceMap* map) const
{
return GetOutputValue("otherOpen0", map);
}
int PerforceCommand::GetOtherUserCheckOutCount(const PerforceMap* map) const
{
return atoi(GetOutputValue("otherOpen", map).c_str());
}
bool PerforceCommand::CurrentActionIsAdd(const PerforceMap* map) const
{
return GetOutputValue("action", map) == "add";
}
bool PerforceCommand::CurrentActionIsEdit(const PerforceMap* map) const
{
return GetOutputValue("action", map) == "edit";
}
bool PerforceCommand::CurrentActionIsDelete(const PerforceMap* map) const
{
return GetOutputValue("action", map) == "delete";
}
bool PerforceCommand::CurrentActionIsMove(const PerforceMap* map) const
{
AZStd::string value = GetOutputValue("action", map);
return value == "move/delete" || value == "move/add";
}
bool PerforceCommand::FileExists() const
{
return m_rawOutput.errorResult.find("no such file(s)") == AZStd::string::npos;
}
bool PerforceCommand::FileExists(const char* searchFile) const
{
return m_rawOutput.errorResult.find(AZStd::string::format("%s - no such file(s)", searchFile)) == AZStd::string::npos;
}
bool PerforceCommand::HasRevision(const PerforceMap* map) const
{
return atoi(GetOutputValue("haveRev", map).c_str()) > 0;
}
bool PerforceCommand::HeadActionIsDelete(const PerforceMap* map) const
{
return GetOutputValue("headAction", map) == "delete";
}
bool PerforceCommand::IsMarkedForAdd() const
{
return m_rawOutput.outputResult.find("can't edit (already opened for add)") != AZStd::string::npos;
}
bool PerforceCommand::NeedsReopening() const
{
return m_rawOutput.outputResult.find("use 'reopen'") != AZStd::string::npos;
}
bool PerforceCommand::IsOpenByOtherUsers(const PerforceMap* map) const
{
return OutputKeyExists("otherOpen", map);
}
bool PerforceCommand::IsOpenByCurrentUser(const PerforceMap* map) const
{
return OutputKeyExists("action", map);
}
bool PerforceCommand::NewFileAfterDeletedRev(const PerforceMap* map) const
{
return (HeadActionIsDelete(map) && !HasRevision(map));
}
bool PerforceCommand::ApplicationFound() const
{
return m_applicationFound;
}
bool PerforceCommand::HasTrustIssue() const
{
if (m_rawOutput.errorResult.find("The authenticity of ") != AZStd::string::npos &&
m_rawOutput.errorResult.find("can't be established,") != AZStd::string::npos)
{
return true;
}
return false;
}
bool PerforceCommand::ExclusiveOpen(const PerforceMap* map) const
{
AZStd::string fileType = GetOutputValue("headType", map);
if (!fileType.empty())
{
size_t modLocation = fileType.find('+');
if (modLocation != AZStd::string::npos)
{
return fileType.find('l', modLocation) != AZStd::string::npos;
}
}
return false;
}
AZStd::string PerforceCommand::GetOutputValue(const AZStd::string& key, const PerforceMap* perforceMap) const
{
if(!perforceMap)
{
perforceMap = &m_commandOutputMap;
}
PerforceMap::const_iterator kvp = perforceMap->find(key);
if (kvp != perforceMap->end())
{
return kvp->second;
}
else
{
return "";
}
}
bool PerforceCommand::OutputKeyExists(const AZStd::string& key, const PerforceMap* perforceMap) const
{
if(!perforceMap)
{
perforceMap = &m_commandOutputMap;
}
PerforceMap::const_iterator kvp = perforceMap->find(key);
return kvp != perforceMap->end();
}
AZStd::vector<PerforceMap>::iterator PerforceCommand::FindMapWithPartiallyMatchingValueForKey(const AZStd::string& key, const AZStd::string& value)
{
for (AZStd::vector<PerforceMap>::iterator perforceIter = m_commandOutputMapList.begin();
perforceIter != m_commandOutputMapList.end(); ++perforceIter)
{
const PerforceMap currentMap = *perforceIter;
PerforceMap::const_iterator kvp = currentMap.find(key);
if (kvp != currentMap.end() && kvp->second.find(value) != AZStd::string::npos)
{
return perforceIter;
}
}
return nullptr;
}
AZStd::string PerforceCommand::CreateChangelistForm(const AZStd::string& client, const AZStd::string& user, const AZStd::string& description)
{
AZStd::string changelistForm = "Change:\tnew\n\nClient:\t";
changelistForm.append(client);
if (!user.empty() && user != "*unknown*")
{
changelistForm.append("\n\nUser:\t");
changelistForm.append(user);
}
changelistForm.append("\n\nStatus:\tnew\n\nDescription:\n\t");
changelistForm.append(description);
changelistForm.append("\n\n");
changelistForm += s_EndOfFileChar;
return changelistForm;
}
void PerforceCommand::ExecuteAdd(const AZStd::string& changelist, const AZStd::string& filePath)
{
m_commandArgs = "add -c " + changelist + " \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteAdd(const AZStd::string& changelist, const AZStd::unordered_set<AZStd::string>& filePaths)
{
m_commandArgs = "add -c " + changelist + " ";
for (const AZStd::string& filePath : filePaths)
{
m_commandArgs += "\"" + filePath + "\" ";
}
ExecuteCommand();
}
void PerforceCommand::ExecuteClaimChangedFile(const AZStd::string& filePath, const AZStd::string& changeList)
{
m_commandArgs = "reopen -c " + changeList + " \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteDelete(const AZStd::string& changelist, const AZStd::string& filePath)
{
m_commandArgs = "delete -c " + changelist + " \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteEdit(const AZStd::string& changelist, const AZStd::string& filePath)
{
m_commandArgs = "edit -c " + changelist + " \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteEdit(const AZStd::string& changelist, const AZStd::unordered_set<AZStd::string>& filePaths)
{
m_commandArgs = "edit -c " + changelist + " ";
for (const AZStd::string& filePath : filePaths)
{
m_commandArgs += "\"" + filePath + "\" ";
}
ExecuteCommand();
}
void PerforceCommand::ExecuteFstat(const AZStd::string& filePath)
{
m_commandArgs = "fstat \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteFstat(const AZStd::unordered_set<AZStd::string>& filePaths)
{
m_commandArgs = "fstat ";
for (const AZStd::string& filePath : filePaths)
{
m_commandArgs += "\"" + filePath + "\" ";
}
ExecuteCommand();
}
void PerforceCommand::ExecuteSync(const AZStd::string& filePath)
{
m_commandArgs = "sync -f \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteMove(const AZStd::string& changelist, const AZStd::string& sourcePath, const AZStd::string& destPath)
{
m_commandArgs = "move -c " + changelist + " \"" + sourcePath + "\"" + " \"" + destPath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteSet()
{
m_commandArgs = "set";
ExecuteRawCommand();
}
void PerforceCommand::ExecuteSet(const AZStd::string& key, const AZStd::string& value)
{
m_commandArgs = "set " + key + '=' + value;
ExecuteIOCommand();
}
void PerforceCommand::ExecuteInfo()
{
m_commandArgs = "info";
ExecuteCommand();
}
void PerforceCommand::ExecuteShortInfo()
{
m_commandArgs = "info -s";
ExecuteCommand();
}
void PerforceCommand::ExecuteTicketStatus()
{
m_commandArgs = "login -s";
ExecuteCommand();
}
void PerforceCommand::ExecuteTrust(bool enable, const AZStd::string& fingerprint)
{
if (enable)
{
m_commandArgs = "trust -i ";
}
else
{
m_commandArgs = "trust -d ";
}
m_commandArgs += fingerprint;
ExecuteCommand();
}
ProcessWatcher* PerforceCommand::ExecuteNewChangelistInput()
{
m_commandArgs = "change -i";
return ExecuteIOCommand();
}
void PerforceCommand::ExecuteNewChangelistOutput()
{
m_commandArgs = "change -o";
ExecuteRawCommand();
}
void PerforceCommand::ExecuteRevert(const AZStd::string& filePath)
{
m_commandArgs = "revert \"" + filePath + "\"";
ExecuteCommand();
}
void PerforceCommand::ExecuteShowChangelists(const AZStd::string& currentUser, const AZStd::string& currentClient)
{
m_commandArgs = "changes -s pending -c " + currentClient;
if (!currentUser.empty() && currentUser != "*unknown*")
{
m_commandArgs += " -u " + currentUser;
}
ExecuteCommand();
}
void PerforceCommand::ThrowWarningMessage()
{
// This can happen all the time, for various reasons. AZ_Warning will actually cause the application to
// take a stack dump and will load pdbs, which can introduce a serious delay during startup. As such,
// we send it as a Trace, not a warning. Background threads retrying may hit this many times.
AZ_TracePrintf(SCC_WINDOW, "Perforce Warning - Command has failed '%s'\n", m_commandArgs.c_str());
}
void PerforceCommand::ExecuteCommand()
{
m_rawOutput.Clear();
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = "p4 -ztag " + m_commandArgs;
processLaunchInfo.m_showWindow = false;
ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
}
ProcessWatcher* PerforceCommand::ExecuteIOCommand()
{
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = "p4 " + m_commandArgs;
processLaunchInfo.m_showWindow = false;
ProcessWatcher* processWatcher = ProcessWatcher::LaunchProcess(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT);
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
return processWatcher;
}
void PerforceCommand::ExecuteRawCommand()
{
m_rawOutput.Clear();
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = "p4 " + m_commandArgs;
processLaunchInfo.m_showWindow = false;
ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
}
AZStd::string PerforceConnection::GetUser() const
{
return GetInfoValue("userName");
}
AZStd::string PerforceConnection::GetClientName() const
{
return GetInfoValue("clientName");
}
AZStd::string PerforceConnection::GetClientRoot() const
{
return GetInfoValue("clientRoot");
}
AZStd::string PerforceConnection::GetServerAddress() const
{
return GetInfoValue("serverAddress");
}
AZStd::string PerforceConnection::GetServerUptime() const
{
return GetInfoValue("serverUptime");
}
AZStd::string PerforceConnection::GetInfoValue(const AZStd::string& key) const
{
PerforceMap::const_iterator kvp = m_infoResultMap.find(key);
if (kvp != m_infoResultMap.end())
{
return kvp->second;
}
return "";
}
bool PerforceConnection::CommandHasOutput() const
{
return m_command.m_rawOutput.HasOutput();
}
bool PerforceConnection::CommandHasError() const
{
return m_command.m_rawOutput.HasError();
}
bool PerforceConnection::CommandHasTrustIssue() const
{
return m_command.HasTrustIssue();
}
bool PerforceConnection::CommandApplicationFound() const
{
return m_command.ApplicationFound();
}
AZStd::string PerforceConnection::GetCommandOutput() const
{
return m_command.m_rawOutput.outputResult;
}
AZStd::string PerforceConnection::GetCommandError() const
{
return m_command.m_rawOutput.errorResult;
}
bool PerforceConnection::CommandHasFailed()
{
if (!CommandHasOutput())
{
if (CommandHasError())
{
AZ_TracePrintf(SCC_WINDOW, "Perforce - Error\n%s\n", GetCommandError().c_str());
}
m_command.ThrowWarningMessage();
return true;
}
return false;
}
} // namespace AzToolsFramework
@@ -0,0 +1,127 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/Process/ProcessCommunicator.h>
namespace AzToolsFramework
{
class ProcessWatcher;
class PerforceCommand
{
public:
ProcessOutput m_rawOutput;
PerforceMap m_commandOutputMap; // doesn't allow duplicate kvp's
AZStd::vector<PerforceMap> m_commandOutputMapList; // allows duplicate kvp's
AZStd::mutex m_commandMutex;
PerforceCommand() {}
virtual ~PerforceCommand() = default;
AZStd::string GetCurrentChangelistNumber(const PerforceMap* map = nullptr) const;
AZStd::string GetHaveRevision(const PerforceMap* map = nullptr) const;
AZStd::string GetHeadRevision(const PerforceMap* map = nullptr) const;
AZStd::string GetOtherUserCheckedOut(const PerforceMap* map = nullptr) const;
int GetOtherUserCheckOutCount(const PerforceMap* map = nullptr) const;
bool CurrentActionIsAdd(const PerforceMap* map = nullptr) const;
bool CurrentActionIsEdit(const PerforceMap* map = nullptr) const;
bool CurrentActionIsDelete(const PerforceMap* map = nullptr) const;
bool CurrentActionIsMove(const PerforceMap* map = nullptr) const;
bool FileExists() const;
bool FileExists(const char* searchFile) const;
bool HasRevision(const PerforceMap* map = nullptr) const;
bool HeadActionIsDelete(const PerforceMap* map = nullptr) const;
bool IsMarkedForAdd() const;
bool NeedsReopening() const;
bool IsOpenByOtherUsers(const PerforceMap* map = nullptr) const;
bool IsOpenByCurrentUser(const PerforceMap* map = nullptr) const;
bool NewFileAfterDeletedRev(const PerforceMap* map = nullptr) const;
bool ApplicationFound() const;
bool HasTrustIssue() const;
bool ExclusiveOpen(const PerforceMap* map = nullptr) const;
AZStd::string GetOutputValue(const AZStd::string& key, const PerforceMap* perforceMap = nullptr) const;
bool OutputKeyExists(const AZStd::string& key, const PerforceMap* perforceMap = nullptr) const;
AZStd::vector<PerforceMap>::iterator FindMapWithPartiallyMatchingValueForKey(const AZStd::string& key, const AZStd::string& value);
AZStd::string CreateChangelistForm(const AZStd::string& client, const AZStd::string& user, const AZStd::string& description);
void ExecuteAdd(const AZStd::string& changelist, const AZStd::string& filePath);
void ExecuteAdd(const AZStd::string& changelist, const AZStd::unordered_set<AZStd::string>& filePaths);
void ExecuteClaimChangedFile(const AZStd::string& filePath, const AZStd::string& changeList);
void ExecuteDelete(const AZStd::string& changelist, const AZStd::string& filePath);
void ExecuteEdit(const AZStd::string& changelist, const AZStd::string& filePath);
void ExecuteEdit(const AZStd::string& changelist, const AZStd::unordered_set<AZStd::string>& filePaths);
void ExecuteFstat(const AZStd::string& filePath);
void ExecuteFstat(const AZStd::unordered_set<AZStd::string>& filePaths);
void ExecuteSync(const AZStd::string& filePath);
void ExecuteMove(const AZStd::string& changelist, const AZStd::string& sourcePath, const AZStd::string& destPath);
void ExecuteSet();
void ExecuteSet(const AZStd::string& key, const AZStd::string& value);
void ExecuteInfo();
void ExecuteShortInfo();
void ExecuteTicketStatus();
void ExecuteTrust(bool enable, const AZStd::string& fingerprint);
ProcessWatcher* ExecuteNewChangelistInput();
void ExecuteNewChangelistOutput();
void ExecuteRevert(const AZStd::string& filePath);
void ExecuteShowChangelists(const AZStd::string& currentUser, const AZStd::string& currentClient);
void ThrowWarningMessage();
protected:
AZStd::string m_commandArgs;
bool m_applicationFound = false;
virtual void ExecuteCommand();
virtual ProcessWatcher* ExecuteIOCommand();
virtual void ExecuteRawCommand();
};
class PerforceConnection
{
public:
PerforceMap m_infoResultMap;
PerforceCommand& m_command;
PerforceConnection() : m_command(m_commandInternal) {}
~PerforceConnection() {}
AZStd::string GetUser() const;
AZStd::string GetClientName() const;
AZStd::string GetClientRoot() const;
AZStd::string GetServerAddress() const;
AZStd::string GetServerUptime() const;
AZStd::string GetInfoValue(const AZStd::string& key) const;
bool CommandHasFailed();
bool CommandHasOutput() const;
bool CommandHasError() const;
bool CommandHasTrustIssue() const;
bool CommandApplicationFound() const;
AZStd::string GetCommandOutput() const;
AZStd::string GetCommandError() const;
protected:
PerforceConnection(PerforceCommand& command) : m_command(command) {}
PerforceCommand m_commandInternal;
};
} // namespace AzToolsFramework
@@ -0,0 +1,66 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzToolsFramework/SourceControl/QtSourceControlNotificationHandler.h>
#include <AzCore/std/string/string.h>
#include <QMessageBox>
#include <QMetaObject>
namespace AzToolsFramework
{
QtSourceControlNotificationHandler::QtSourceControlNotificationHandler(QWidget* pParent)
: QObject(pParent)
{
}
QtSourceControlNotificationHandler::~QtSourceControlNotificationHandler()
{
Shutdown();
}
void QtSourceControlNotificationHandler::Init()
{
SourceControlNotificationBus::Handler::BusConnect();
}
void QtSourceControlNotificationHandler::Shutdown()
{
SourceControlNotificationBus::Handler::BusDisconnect();
}
void QtSourceControlNotificationHandler::RequestTrust(const char* fingerprint)
{
QString message = QString("%1\n\n%2\n\n%3")
.arg("The fingerprint for the key sent to your client is:")
.arg(fingerprint)
.arg("Establish Trust?");
auto azFingerprint = AZStd::string(fingerprint);
auto userAnswer = QMessageBox::question(qobject_cast<QWidget*>(parent()), "Establish Trust?", message,
QMessageBox::StandardButton::Yes, QMessageBox::StandardButton::No);
using SCRequestBus = AzToolsFramework::SourceControlConnectionRequestBus;
SCRequestBus::Broadcast(&SCRequestBus::Events::EnableTrust, userAnswer == QMessageBox::Yes, azFingerprint);
if (userAnswer == QMessageBox::No)
{
SCRequestBus::Broadcast(&SCRequestBus::Events::EnableSourceControl, false);
}
}
} // namespace AzToolsFramework
#include "SourceControl/moc_QtSourceControlNotificationHandler.cpp"
@@ -0,0 +1,40 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <QString>
#include <QObject>
#endif
namespace AzToolsFramework
{
class QtSourceControlNotificationHandler
: public QObject
, private SourceControlNotificationBus::Handler
{
Q_OBJECT
public:
explicit QtSourceControlNotificationHandler(QWidget* pParent);
virtual ~QtSourceControlNotificationHandler();
void Init();
void Shutdown();
private:
// AzToolsFramework::SourceControlNotificationBus::Handler
void RequestTrust(const char* fingerprint) override;
};
} // namespace AzToolsFramework
@@ -0,0 +1,269 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AzToolsFramework
{
//! Name of the AZ Trace window for source control messages
static constexpr char SCC_WINDOW[] = "Source Control";
enum SourceControlStatus
{
SCS_OpSuccess, // No errors reported
SCS_OpNotSupported, // Operation not supported by the source control provider
SCS_CertificateInvalid, // Trust certificate is invalid
SCS_ProviderIsDown, // source control provider is down
SCS_ProviderNotFound, // source control provider not found
SCS_ProviderError, // there was an error processing your request
SCS_NUM_ERRORS, // add errors above this enum
};
enum SourceControlFlags
{
SCF_OutOfDate = (1 << 0), // the file was out of date
SCF_Writeable = (1 << 1), // the file is writable on disk
SCF_MultiCheckOut = (1 << 2), // this file allows multiple owners
SCF_OtherOpen = (1 << 3), // someone else has this file open
SCF_PendingAdd = (1 << 4), // file marked for add
SCF_PendingDelete = (1 << 5), // file marked for removal
SCF_OpenByUser = (1 << 6), // currently open for checkout / staging
SCF_Tracked = (1 << 7), // file is under source control
SCF_PendingMove = (1 << 8), // file marked for move
};
struct SourceControlFileInfo
{
SourceControlStatus m_status;
unsigned int m_flags;
AZStd::string m_filePath;
AZStd::string m_StatusUser; // informational - this is the user that caused the above status.
// secondary use of m_StatusUser is to signify that this file is being worked on by other users, simultaneously.
SourceControlFileInfo()
: m_status(SCS_ProviderIsDown)
, m_flags((SourceControlFlags)0)
{
}
SourceControlFileInfo(const char* fullFilePath)
: m_status(SCS_ProviderIsDown)
, m_filePath(fullFilePath)
, m_flags((SourceControlFlags)0)
{
}
SourceControlFileInfo(const SourceControlFileInfo& rhs)
{
*this = rhs;
}
SourceControlFileInfo(SourceControlFileInfo&& rhs)
: m_status(rhs.m_status)
, m_flags(rhs.m_flags)
, m_filePath(AZStd::move(rhs.m_filePath))
, m_StatusUser(AZStd::move(rhs.m_StatusUser))
{
}
SourceControlFileInfo& operator=(const SourceControlFileInfo& rhs)
{
m_status = rhs.m_status;
m_flags = rhs.m_flags;
m_filePath = rhs.m_filePath;
m_StatusUser = rhs.m_StatusUser;
return *this;
}
bool CompareStatus(SourceControlStatus status) const { return m_status == status; }
bool IsReadOnly() const { return !HasFlag(SCF_Writeable); }
bool IsLockedByOther() const { return HasFlag(SCF_OtherOpen) && !HasFlag(SCF_MultiCheckOut); }
bool IsManaged() const { return HasFlag(SCF_Tracked); }
bool HasFlag(SourceControlFlags flag) const { return ((m_flags & flag) != 0); }
};
// use bind if you need additional context.
typedef AZStd::function<void(bool success, SourceControlFileInfo info)> SourceControlResponseCallback;
typedef AZStd::function<void(bool success, AZStd::vector<SourceControlFileInfo> info)> SourceControlResponseCallbackBulk;
enum class SourceControlSettingStatus : int
{
Invalid,
PERFORCE_BEGIN,
Unset,
None,
Set,
Config,
PERFORCE_END,
};
struct SourceControlSettingInfo
{
SourceControlSettingStatus m_status = SourceControlSettingStatus::Invalid;
AZStd::string m_value;
AZStd::string m_context;
SourceControlSettingInfo() = default;
//! is this value actually present and usable?
bool IsAvailable() const
{
return (m_status != SourceControlSettingStatus::Invalid) && (m_status != SourceControlSettingStatus::Unset) && (!m_value.empty());
}
//! Are we able to actually change this value without messing with global env or registry?
bool IsSettable() const
{
if (m_status == SourceControlSettingStatus::Invalid)
{
return false;
}
return ((m_status == SourceControlSettingStatus::Unset) || (m_status == SourceControlSettingStatus::Set));
}
};
typedef AZStd::function<void(const SourceControlSettingInfo& info)> SourceControlSettingCallback;
enum class SourceControlState : int
{
Disabled,
ConfigurationInvalid,
Active,
};
//! SourceControlCommands
//! This bus handles messages relating to source control commands
//! source control commands are ASYNCHRONOUS
//! do not block the main thread waiting for a response, it is not okay
//! you will not get a message delivered unless you tick the tickbus anyway!
class SourceControlCommands
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; // there's only one source control listener right now
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // theres only one source control listener right now
typedef AZStd::recursive_mutex MutexType;
virtual ~SourceControlCommands() {}
//! Get information on the file state
virtual void GetFileInfo(const char* fullFilePath, const SourceControlResponseCallback& respCallback) = 0;
//! Get information on the file state for multiple files. Path(s) may contain wildcards
virtual void GetBulkFileInfo(const AZStd::unordered_set<AZStd::string>& fullFilePaths, const SourceControlResponseCallbackBulk& respCallback) = 0;
//! Attempt to make a file ready for editing
virtual void RequestEdit(const char* fullFilePath, bool allowMultiCheckout, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to make a set of files ready for editing
virtual void RequestEditBulk(const AZStd::unordered_set<AZStd::string>& fullFilePaths, bool allowMultiCheckout, const SourceControlResponseCallbackBulk& respCallback) = 0;
//! Attempt to delete a file
virtual void RequestDelete(const char* fullFilePath, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to delete a file
//! @param skipReadOnly If source control is disabled and we're using the local file component, this will skip changes to files which are readonly
virtual void RequestDeleteExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to delete multiple files. Path may contain wildcards
virtual void RequestDeleteBulk(const char* fullFilePath, const SourceControlResponseCallbackBulk& respCallback) = 0;
//! Attempt to delete multiple files. Path may contain wildcards
//! @param skipReadOnly If source control is disabled and we're using the local file component, this will skip changes to files which are readonly
virtual void RequestDeleteBulkExtended(const char* fullFilePath, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) = 0;
//! Attempt to revert a file
virtual void RequestRevert(const char* fullFilePath, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to get latest revision of a file
virtual void RequestLatest(const char* fullFilePath, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to rename or move a file
virtual void RequestRename(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to rename or move a file
//! @param skipReadOnly If source control is disabled and we're using the local file component, this will skip changes to files which are readonly
virtual void RequestRenameExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallback& respCallback) = 0;
//! Attempt to rename or move multiple files. Path may contain wildcards
virtual void RequestRenameBulk(const char* sourcePathFull, const char* destPathFull, const SourceControlResponseCallbackBulk& respCallback) = 0;
//! Attempt to rename or move multiple files. Path may contain wildcards
//! @param skipReadOnly If source control is disabled and we're using the local file component, this will skip changes to files which are readonly
virtual void RequestRenameBulkExtended(const char* sourcePathFull, const char* destPathFull, bool skipReadOnly, const SourceControlResponseCallbackBulk& respCallback) = 0;
};
using SourceControlCommandBus = AZ::EBus<SourceControlCommands>;
//! SourceControlConnectionRequests
//! This bus handles messages relating to source control connectivity
class SourceControlConnectionRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
virtual ~SourceControlConnectionRequests() {}
//! Suspend / Resume source control operations
virtual void EnableSourceControl(bool enable) = 0;
//! Returns if source control operations are enabled
virtual bool IsActive() const = 0;
//! Enable or disable trust of an SSL connection
virtual void EnableTrust(bool enable, AZStd::string fingerprint) = 0;
//! Attempt to set connection setting 'key' to 'value'
virtual void SetConnectionSetting(const char* key, const char* value, const SourceControlSettingCallback& respCallBack) = 0;
//! Attempt to get connection setting by key
virtual void GetConnectionSetting(const char* key, const SourceControlSettingCallback& respCallBack) = 0;
//! Returns if source control is disabled, has invalid configurations, or enabled
virtual SourceControlState GetSourceControlState() const { return SourceControlState::Disabled; }
};
using SourceControlConnectionRequestBus = AZ::EBus<SourceControlConnectionRequests>;
//! SourceControlNotifications
//! Outgoing messages from source control
class SourceControlNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
virtual ~SourceControlNotifications() {}
//! Request to trust source control key with provided fingerprint
virtual void RequestTrust(const char* /*fingerprint*/) {}
//! Notify listeners that our connectivity state has changed
virtual void ConnectivityStateChanged(const SourceControlState /*connected*/) {}
};
using SourceControlNotificationBus = AZ::EBus<SourceControlNotifications>;
}; // namespace AzToolsFramework