diff --git a/p4-fusion/branch_set.cc b/p4-fusion/branch_set.cc index a16b21d3..45a00aaf 100644 --- a/p4-fusion/branch_set.cc +++ b/p4-fusion/branch_set.cc @@ -10,16 +10,6 @@ static const std::string EMPTY_STRING = ""; static const std::array INVALID_BRANCH_PATH { EMPTY_STRING, EMPTY_STRING }; -std::vector BranchedFileGroup::GetRelativeFileNames() -{ - std::vector ret; - for (auto& fileData : files) - { - ret.push_back(fileData.GetRelativePath()); - } - return ret; -} - ChangedFileGroups::ChangedFileGroups() : totalFileCount(0) { @@ -295,7 +285,7 @@ std::unique_ptr BranchSet::ParseAffectedFiles(const std::vect // It's a valid destination to a branch. // Make sure the relative path is set. - fileData.SetRelativePath(branchPath[1]); + fileData.SetRelativeDepotPath(branchPath[1]); bool needsHandling = true; if (fileData.IsIntegrated()) @@ -326,7 +316,7 @@ std::unique_ptr BranchSet::ParseAffectedFiles(const std::vect { // It's a non-branching setup. // Make sure the relative path is set. - fileData.SetRelativePath(relativeDepotPath); + fileData.SetRelativeDepotPath(relativeDepotPath); branchMap.addTarget(EMPTY_STRING, fileData); } } diff --git a/p4-fusion/branch_set.h b/p4-fusion/branch_set.h index 5adae31c..7fe1432f 100644 --- a/p4-fusion/branch_set.h +++ b/p4-fusion/branch_set.h @@ -1,101 +1,98 @@ -/* - * Copyright (c) 2022 Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -#pragma once - -#include -#include -#include -#include -#include - -#include "commands/file_map.h" -#include "commands/file_data.h" -#include "commands/stream_result.h" -#include "utils/std_helpers.h" - -struct BranchedFileGroup -{ - // If a BranchedFiles collection hasSource == true, - // then all files in this collection MUST be a merge - // from the given source branch to the target branch. - // These branch names will be the Git branch names. - std::string sourceBranch; - std::string targetBranch; - bool hasSource; - std::vector files; - - // Get all the relative file names from each of the file data. - std::vector GetRelativeFileNames(); -}; - -struct ChangedFileGroups -{ -private: - ChangedFileGroups(); - -public: - std::vector branchedFileGroups; - int totalFileCount; - - // When all the file groups have finished being used, - // only then can we safely clear out the data. - void Clear(); - - ChangedFileGroups(std::vector& groups, int totalFileCount); - - static std::unique_ptr Empty() { return std::unique_ptr(new ChangedFileGroups); }; -}; - -struct Branch -{ -public: - const std::string depotBranchPath; - const std::string gitAlias; - - Branch(const std::string& branch, const std::string& alias); - - // splitBranchPath If the relativeDepotPath matches, returns {branch alias, branch file path}. - // Otherwise, returns {"", ""} - std::array SplitBranchPath(const std::string& relativeDepotPath) const; -}; - -// A singular view on the branches and a base view (acts as a filter to trim down affected files). -// Maps a changed file state to a list of resulting branches and affected files. -struct BranchSet -{ -private: - // Technically, these should all be const. - const bool m_includeBinaries; - std::string m_basePath; - const std::vector m_branches; - const std::vector m_mappings; - const std::vector m_exclusions; - FileMap m_view; - - // stripBasePath remove the base path from the depot path, or "" if not in the base path. - std::string stripBasePath(const std::string& depotPath) const; - - // splitBranchPath extract the branch name and path under the branch (no leading '/' on the path) - // relativeDepotPath - already stripped from running stripBasePath. - std::array splitBranchPath(const std::string& relativeDepotPath) const; - -public: - BranchSet(std::vector& clientViewMapping, const std::string& baseDepotPath, const std::vector& branches, const std::vector& mappings, const std::vector& exclusions, const bool includeBinaries); - - // HasMergeableBranch is there a branch model that requires integration history? - bool HasMergeableBranch() const { return !m_branches.empty(); }; - - int Count() const { return m_branches.size(); }; - - // ParseAffectedFiles create collections of merges and commits. - // Breaks up the files into those that are within the view, with each item in the - // list is its own target Git branch. - // This also has the side-effect of populating the relative path value in the file data. - // ... the FileData object is copied, but it's underlying shared data is shared. So, this - // breaks the const. - std::unique_ptr ParseAffectedFiles(const std::vector& cl) const; -}; +/* + * Copyright (c) 2022 Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "commands/file_map.h" +#include "commands/file_data.h" +#include "commands/stream_result.h" +#include "utils/std_helpers.h" + +struct BranchedFileGroup +{ + // If a BranchedFiles collection hasSource == true, + // then all files in this collection MUST be a merge + // from the given source branch to the target branch. + // These branch names will be the Git branch names. + std::string sourceBranch; + std::string targetBranch; + bool hasSource; + std::vector files; +}; + +struct ChangedFileGroups +{ +private: + ChangedFileGroups(); + +public: + std::vector branchedFileGroups; + int totalFileCount; + + // When all the file groups have finished being used, + // only then can we safely clear out the data. + void Clear(); + + ChangedFileGroups(std::vector& groups, int totalFileCount); + + static std::unique_ptr Empty() { return std::unique_ptr(new ChangedFileGroups); }; +}; + +struct Branch +{ +public: + const std::string depotBranchPath; + const std::string gitAlias; + + Branch(const std::string& branch, const std::string& alias); + + // splitBranchPath If the relativeDepotPath matches, returns {branch alias, branch file path}. + // Otherwise, returns {"", ""} + std::array SplitBranchPath(const std::string& relativeDepotPath) const; +}; + +// A singular view on the branches and a base view (acts as a filter to trim down affected files). +// Maps a changed file state to a list of resulting branches and affected files. +struct BranchSet +{ +private: + // Technically, these should all be const. + const bool m_includeBinaries; + std::string m_basePath; + const std::vector m_branches; + const std::vector m_mappings; + const std::vector m_exclusions; + FileMap m_view; + + // stripBasePath remove the base path from the depot path, or "" if not in the base path. + std::string stripBasePath(const std::string& depotPath) const; + + // splitBranchPath extract the branch name and path under the branch (no leading '/' on the path) + // relativeDepotPath - already stripped from running stripBasePath. + std::array splitBranchPath(const std::string& relativeDepotPath) const; + +public: + BranchSet(std::vector& clientViewMapping, const std::string& baseDepotPath, const std::vector& branches, const std::vector& mappings, const std::vector& exclusions, const bool includeBinaries); + + // HasMergeableBranch is there a branch model that requires integration history? + bool HasMergeableBranch() const { return !m_branches.empty(); }; + + int Count() const { return m_branches.size(); }; + + // ParseAffectedFiles create collections of merges and commits. + // Breaks up the files into those that are within the view, with each item in the + // list is its own target Git branch. + // This also has the side-effect of populating the relative path value in the file data. + // ... the FileData object is copied, but it's underlying shared data is shared. So, this + // breaks the const. + std::unique_ptr ParseAffectedFiles(const std::vector& cl) const; +}; diff --git a/p4-fusion/commands/file_data.cc b/p4-fusion/commands/file_data.cc index 4c8ff762..7e23fee7 100644 --- a/p4-fusion/commands/file_data.cc +++ b/p4-fusion/commands/file_data.cc @@ -76,7 +76,7 @@ void FileData::SetPendingDownload() } } -void FileData::SetRelativePath(std::string& relativePath) +void FileData::SetRelativeDepotPath(const std::string& relativePath) { m_data->relativePath = relativePath; } diff --git a/p4-fusion/commands/file_data.h b/p4-fusion/commands/file_data.h index 82e4781d..25e1b0b8 100644 --- a/p4-fusion/commands/file_data.h +++ b/p4-fusion/commands/file_data.h @@ -76,7 +76,7 @@ struct FileData FileData& operator=(FileData& other); void SetFromDepotFile(const std::string& fromDepotFile, const std::string& fromRevision); - void SetRelativePath(std::string& relativePath); + void SetRelativeDepotPath(const std::string& relativePath); void SetFakeIntegrationDeleteAction() { m_data->SetAction(FAKE_INTEGRATION_DELETE_ACTION_NAME); }; // moves the argument's data into this file data structure. @@ -88,7 +88,7 @@ struct FileData const std::string& GetDepotFile() const { return m_data->depotFile; }; const std::string& GetRevision() const { return m_data->revision; }; const FileAction GetAction() const { return m_data->actionCategory; }; - const std::string& GetRelativePath() const { return m_data->relativePath; }; + const std::string& GetRelativeDepotPath() const { return m_data->relativePath; }; const std::vector& GetContents() const { return m_data->contents; }; bool IsDeleted() const { return m_data->isDeleted; }; bool IsIntegrated() const { return m_data->isIntegrated; }; diff --git a/p4-fusion/main.cc b/p4-fusion/main.cc index d7b398b6..01dfae6e 100644 --- a/p4-fusion/main.cc +++ b/p4-fusion/main.cc @@ -15,6 +15,7 @@ #include "utils/std_helpers.h" #include "utils/timer.h" #include "utils/arguments.h" +#include "utils/p4_helpers.h" #include "thread_pool.h" #include "p4_api.h" @@ -390,13 +391,14 @@ int Main(int argc, char** argv) for (auto& file : branchGroup.files) { + const std::string unescapedPath = P4Unescape(file.GetRelativeDepotPath()); if (file.IsDeleted()) { - git.RemoveFileFromIndex(file.GetRelativePath()); + git.RemoveFileFromIndex(unescapedPath); } else { - git.AddFileToIndex(file.GetRelativePath(), file.GetContents(), file.IsExecutable()); + git.AddFileToIndex(unescapedPath, file.GetContents(), file.IsExecutable()); } // No use for keeping the contents in memory once it has been added diff --git a/p4-fusion/utils/p4_helpers.cc b/p4-fusion/utils/p4_helpers.cc new file mode 100644 index 00000000..e07e2569 --- /dev/null +++ b/p4-fusion/utils/p4_helpers.cc @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +#include "p4_helpers.h" + +#include "std_helpers.h" + +std::string P4Unescape(std::string p4path) +{ + STDHelpers::ReplaceAll(p4path, "%40", "@"); + STDHelpers::ReplaceAll(p4path, "%23", "#"); + STDHelpers::ReplaceAll(p4path, "%2A", "*"); + // Replace %25 last to avoid interfering with other escape sequences + STDHelpers::ReplaceAll(p4path, "%25", "%"); + return p4path; +} \ No newline at end of file diff --git a/p4-fusion/utils/p4_helpers.h b/p4-fusion/utils/p4_helpers.h new file mode 100644 index 00000000..408e4186 --- /dev/null +++ b/p4-fusion/utils/p4_helpers.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2022 Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +#pragma once + +#include + +std::string P4Unescape(std::string p4path); \ No newline at end of file diff --git a/p4-fusion/utils/std_helpers.cc b/p4-fusion/utils/std_helpers.cc index af488112..25eb55fb 100644 --- a/p4-fusion/utils/std_helpers.cc +++ b/p4-fusion/utils/std_helpers.cc @@ -67,6 +67,25 @@ void STDHelpers::StripSurrounding(std::string& source, const char c) } } +std::string STDHelpers::ToLower(const std::string& source) +{ + std::string result = source; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return result; +} + +void STDHelpers::ReplaceAll(std::string& str, const std::string& find, const std::string& replace) +{ + std::string::size_type pos; + std::string::size_type startPos = 0; + + while ((pos = str.find(find, startPos)) != std::string::npos) + { + str.replace(pos, find.length(), replace); + startPos = pos + replace.length(); + } +} + std::array STDHelpers::SplitAt(const std::string& source, const char c, const size_t startAt) { size_t pos = source.find(c, startAt); diff --git a/p4-fusion/utils/std_helpers.h b/p4-fusion/utils/std_helpers.h index 1056a628..26ae6087 100644 --- a/p4-fusion/utils/std_helpers.h +++ b/p4-fusion/utils/std_helpers.h @@ -1,26 +1,28 @@ -/* - * Copyright (c) 2022 Salesforce, Inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -#pragma once - -#include -#include -#include - -class STDHelpers -{ -public: - static bool EndsWith(const std::string& str, const std::string& checkStr); - static bool StartsWith(const std::string& str, const std::string& checkStr); - static bool Contains(const std::string& str, const std::string& subStr); - static void Erase(std::string& source, const std::string& subStr); - static void StripSurrounding(std::string& source, const char c); - - // Split the source into two strings at the first character 'c' after position 'startAt'. The 'c' character is - // not included in the returned strings. Text before the 'startAt' will not be included. - static std::array SplitAt(const std::string& source, const char c, const size_t startAt = 0); - static std::vector SplitOnDelim(const std::string& source, const char delim); -}; +/* + * Copyright (c) 2022 Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +#pragma once + +#include +#include +#include + +class STDHelpers +{ +public: + static bool EndsWith(const std::string& str, const std::string& checkStr); + static bool StartsWith(const std::string& str, const std::string& checkStr); + static bool Contains(const std::string& str, const std::string& subStr); + static void Erase(std::string& source, const std::string& subStr); + static void StripSurrounding(std::string& source, const char c); + static std::string ToLower(const std::string& source); + static void ReplaceAll(std::string& str, const std::string& find, const std::string& replace); + + // Split the source into two strings at the first character 'c' after position 'startAt'. The 'c' character is + // not included in the returned strings. Text before the 'startAt' will not be included. + static std::array SplitAt(const std::string& source, const char c, const size_t startAt = 0); + static std::vector SplitOnDelim(const std::string& source, const char delim); +};