Files
eepp/src/tools/ecode/plugins/git/git.hpp
2025-08-22 01:33:51 -03:00

377 lines
11 KiB
C++

#ifndef ECODE_GIT_HPP
#define ECODE_GIT_HPP
#include <cstdint>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include <eepp/system/log.hpp>
#include <eepp/system/mutex.hpp>
using namespace EE::System;
namespace ecode {
#define git_xy( x, y ) ( ( (uint16_t)x ) << 8 | y )
class Git {
public:
struct Blame {
Blame( const std::string& error );
Blame( std::string&& author, std::string&& authorEmail, std::string&& date,
std::string&& commitHash, std::string&& commitShortHash, std::string&& commitMessage,
std::size_t line );
std::string author;
std::string authorEmail;
std::string date;
std::string commitHash;
std::string commitShortHash;
std::string commitMessage;
std::string error;
std::size_t line{ 0 };
};
enum class GitStatusChar : char {
Unknown = ' ',
Modified = 'M',
Added = 'A',
Renamed = 'R',
TypeChanged = 'T',
UpdatedUnmerged = 'U',
Deleted = 'D',
Untracked = '?',
Ignored = 'I',
Copied = 'C',
ModifiedSubmodule = 'm',
};
enum class GitStatus {
Unmerge_BothDeleted,
Unmerge_AddedByUs,
Unmerge_DeletedByThem,
Unmerge_AddedByThem,
Unmerge_DeletedByUs,
Unmerge_BothAdded,
Unmerge_BothModified,
Index_Modified,
Index_Added,
Index_Deleted,
Index_Renamed,
Index_Copied,
Index_ModifiedSubmodule,
WorkingTree_Modified,
WorkingTree_Deleted,
WorkingTree_IntentToAdd,
WorkingTree_ModifiedSubmodule,
Untracked,
Ignored,
NotSet
};
enum class GitStatusType {
Staged = 1 << 1,
Changed = 1 << 2,
Untracked = 1 << 3,
Unmerged = 1 << 4,
Ignored = 1 << 5,
};
static constexpr auto STATUS_TYPE_ALL =
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Staged ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Changed ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Untracked ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Unmerged ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Ignored );
static constexpr auto ANY = 0;
enum StatusXY : uint16_t {
A = git_xy( ' ', 'A' ), // modified, added not updated
M = git_xy( ' ', 'M' ), // modified, modified not updated
D = git_xy( ' ', 'D' ), // modified, deleted not updated
m = git_xy( ' ', 'm' ), // modified, modified submodule not updated
DD = git_xy( 'D', 'D' ), // unmerged, both deleted
AU = git_xy( 'A', 'U' ), // unmerged, added by us
UD = git_xy( 'U', 'D' ), // unmerged, deleted by them
UA = git_xy( 'U', 'A' ), // unmerged, added by them
DU = git_xy( 'D', 'U' ), // unmerged, deleted by us
AA = git_xy( 'A', 'A' ), // unmerged, both added
UU = git_xy( 'U', 'U' ), // unmerged, both modified
QQ = git_xy( '?', '?' ), // untracked
II = git_xy( '!', '!' ), // ignored
SM = git_xy( 'M', ANY ), // staged modified
ST = git_xy( 'T', ANY ), // staged type changed
SA = git_xy( 'A', ANY ), // staged added
SD = git_xy( 'D', ANY ), // staged deleted
SR = git_xy( 'R', ANY ), // staged renamed
SC = git_xy( 'C', ANY ), // staged copied
SMM = git_xy( 'm', ANY ), // staged modified submodule
};
struct GitStatusReport {
GitStatus status = GitStatus::NotSet;
GitStatusType type = GitStatusType::Untracked;
GitStatusChar symbol = GitStatusChar::Unknown;
bool operator==( const GitStatusReport& other ) const {
return status == other.status && symbol == other.symbol && type == other.type;
}
};
struct DiffFile {
std::string file;
int inserts{ 0 };
int deletes{ 0 };
GitStatusReport report;
bool operator==( const DiffFile& other ) const {
return file == other.file && inserts == other.inserts && deletes == other.deletes &&
report == other.report;
}
};
using RepositoryName = std::string;
using FilesStatus = std::map<RepositoryName, std::vector<DiffFile>>;
struct Status {
int totalInserts{ 0 };
int totalDeletions{ 0 };
FilesStatus files;
bool operator==( const Status& other ) const {
return totalInserts == other.totalInserts && totalDeletions == other.totalDeletions &&
files == other.files;
}
bool empty() { return totalInserts == 0 && totalDeletions == 0 && files.empty(); }
bool hasStagedChanges( const std::string& repoName ) {
auto found = files.find( repoName );
if ( found != files.end() ) {
for ( const auto& file : found->second ) {
if ( file.report.type == GitStatusType::Staged )
return true;
}
}
return false;
}
};
struct Result {
std::string result;
int returnCode = 0;
bool success() const { return returnCode == EXIT_SUCCESS; }
bool fail() const { return !success(); }
};
struct CheckoutResult : public Result {
std::string branch;
};
struct CountResult : public Result {
int64_t behind{ 0 };
int64_t ahead{ 0 };
};
enum RefType {
Head = 0x1,
Remote = 0x2,
Tag = 0x4,
Stash = 0x8,
All = 0xF,
};
static constexpr const char* HEAD = "head";
static constexpr const char* REMOTE = "remote";
static constexpr const char* TAG = "tag";
static constexpr const char* STASH = "stash";
static constexpr const char* ALL = "all";
static const char* refTypeToString( RefType type ) {
switch ( type ) {
case Head:
return HEAD;
case Remote:
return REMOTE;
case Tag:
return TAG;
case Stash:
return STASH;
case All:
return ALL;
}
return nullptr;
}
struct Branch {
/** Branch name */
std::string name;
/** remote name, will be empty for local branches */
std::string remote;
/** Ref type @see RefType */
RefType type = All;
/** last commit on this branch, may be empty **/
std::string lastCommit;
/** date string in yyyy-mm-dd hh:mn */
std::string date;
/** if it's HEAD how much ahead and behind the current local branch is against remote */
int64_t ahead{ 0 };
int64_t behind{ 0 };
bool gone{ false };
const char* typeStr() const { return refTypeToString( type ); }
bool isEmpty() const { return name.empty(); }
};
enum DiffMode { DiffHead, DiffStaged };
Git( const std::string& projectDir = "", const std::string& gitPath = "" );
int git( const std::string& args, const std::string& projectDir, std::string& buf ) const;
void gitSubmodules( const std::string& args, const std::string& projectDir, std::string& buf );
bool isGitRepo( const std::string& projectDir );
Blame blame( const std::string& filepath, std::size_t line ) const;
std::string branch( const std::string& projectDir = "" );
std::unordered_map<std::string, std::string> branches( const std::vector<std::string>& repos );
Status status( bool recurseSubmodules, const std::string& projectDir = "" );
Result add( std::vector<std::string> files, const std::string& projectDir = "" );
Result stash( std::vector<std::string> files, const std::string& projectDir = "" );
Result restore( std::vector<std::string> files, const std::string& projectDir = "" );
Result restore( const std::string& file, const std::string& projectDir = "" );
Result reset( std::vector<std::string> files, const std::string& projectDir = "" );
Result diff( DiffMode mode, const std::string& projectDir = "" );
Result diff( const std::string& file, bool isStaged, const std::string& projectDir = "" );
Result createBranch( const std::string& branchName, bool checkout = false,
const std::string& projectDir = "" );
Result renameBranch( const std::string& branch, const std::string& newName,
const std::string& projectDir = "" );
Result deleteBranch( const std::string& branch, const std::string& projectDir = "" );
Result mergeBranch( const std::string& branch, bool fastForward = false,
const std::string& projectDir = "" );
Result commit( const std::string& commitMsg, bool amend, bool byPassCommitHook,
const std::string& projectDir = "" );
Result fetch( const std::string& projectDir = "" );
Result fastForwardMerge( const std::string& projectDir = "" );
Result updateRef( const std::string& headBranch, const std::string& toCommit,
const std::string& projectDir = "" );
CountResult branchHistoryPosition( const std::string& localBranch,
const std::string& remoteBranch,
const std::string& projectDir = "" );
CountResult branchHistoryPosition( const Git::Branch& branch,
const std::string& projectDir = "" );
bool setProjectPath( const std::string& projectPath );
const std::string& getGitPath() const;
const std::string& getProjectPath() const;
const std::string& getGitFolder() const;
std::string setSafeDirectory( const std::string& projectDir ) const;
Result pull( const std::string& projectDir = "" );
Result push( const std::string& projectDir = "" );
Result pushNewBranch( const std::string& branch, const std::string& projectDir );
CheckoutResult checkout( const std::string& branch, const std::string& projectDir = "" ) const;
CheckoutResult checkoutAndCreateLocalBranch( const std::string& remoteBranch,
const std::string& newBranch = "",
const std::string& projectDir = "" ) const;
CheckoutResult checkoutNewBranch( const std::string& newBranch,
const std::string& fromBranch = "",
const std::string& projectDir = "" );
/**
* @brief get all local and remote branches
*/
std::vector<Branch> getAllBranches( const std::string& projectDir = "" );
/**
* @brief get all local and remote branches + tags
*/
std::vector<Branch> getAllBranchesAndTags( RefType ref = RefType::All,
std::string_view filterBranch = {},
const std::string& projectDir = "" );
std::vector<std::string> fetchSubModules( const std::string& projectDir );
std::vector<std::string> getSubModules( const std::string& projectDir = "" );
std::string repoName( std::string file, bool allowExactMatch = false,
const std::string& projectDir = "" );
std::string repoPath( const std::string& file );
bool hasSubmodules( const std::string& projectDir );
Result gitSimple( const std::string& cmd, const std::string& projectDir );
GitStatusReport statusFromShortStatusStr( const std::string_view& statusStr );
void setSilent( bool set ) { mSilent = set; }
bool isSilent() const { return mSilent; }
Result stashPush( std::vector<std::string> files, const std::string& name, bool keepIndex,
const std::string& projectDir = "" );
Result stashApply( const std::string& stashId, bool restoreIndex,
const std::string& projectDir = "" );
Result stashDrop( const std::string& stashId, const std::string& projectDir = "" );
protected:
std::string mGitPath;
std::string mProjectPath;
std::string mGitFolder;
std::vector<std::string> mSubModules;
Mutex mSubModulesMutex;
bool mSubModulesUpdated{ false };
bool mSilent{ false };
};
} // namespace ecode
#endif