mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-02 11:36:30 +03:00
377 lines
11 KiB
C++
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
|