// Copyright 2019 Roman Perepelitsa. // // This file is part of GitStatus. // // GitStatus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // GitStatus is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with GitStatus. If not, see . #include "git.h" #include #include #include #include #include #include #include #include #include #include "arena.h" #include "check.h" #include "print.h" #include "scope_guard.h" namespace gitstatus { const char* GitError() { const git_error* err = git_error_last(); return err && err->message ? err->message : "unknown error"; } std::string RepoState(git_repository* repo) { Arena arena; StringView gitdir(git_repository_path(repo)); // These names mostly match gitaction in vcs_info: // https://github.com/zsh-users/zsh/blob/master/Functions/VCS_Info/Backends/VCS_INFO_get_data_git. auto State = [&]() { switch (git_repository_state(repo)) { case GIT_REPOSITORY_STATE_NONE: return ""; case GIT_REPOSITORY_STATE_MERGE: return "merge"; case GIT_REPOSITORY_STATE_REVERT: return "revert"; case GIT_REPOSITORY_STATE_REVERT_SEQUENCE: return "revert-seq"; case GIT_REPOSITORY_STATE_CHERRYPICK: return "cherry"; case GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE: return "cherry-seq"; case GIT_REPOSITORY_STATE_BISECT: return "bisect"; case GIT_REPOSITORY_STATE_REBASE: return "rebase"; case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE: return "rebase-i"; case GIT_REPOSITORY_STATE_REBASE_MERGE: return "rebase-m"; case GIT_REPOSITORY_STATE_APPLY_MAILBOX: return "am"; case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE: return "am/rebase"; } return "action"; }; auto DirExists = [&](StringView name) { int fd = open(arena.StrCat(gitdir, "/", name), O_DIRECTORY | O_CLOEXEC); if (fd < 0) return false; CHECK(!close(fd)) << Errno(); return true; }; auto ReadFile = [&](StringView name) { std::ifstream strm(arena.StrCat(gitdir, "/", name)); std::string res; strm >> res; return res; }; std::string next; std::string last; if (DirExists("rebase-merge")) { next = ReadFile("rebase-merge/msgnum"); last = ReadFile("rebase-merge/end"); } else if (DirExists("rebase-apply")) { next = ReadFile("rebase-apply/next"); last = ReadFile("rebase-apply/last"); } std::ostringstream res; res << State(); if (!next.empty() && !last.empty()) res << ' ' << next << '/' << last; return res.str(); } size_t CountRange(git_repository* repo, const std::string& range) { git_revwalk* walk = nullptr; VERIFY(!git_revwalk_new(&walk, repo)) << GitError(); ON_SCOPE_EXIT(=) { git_revwalk_free(walk); }; VERIFY(!git_revwalk_push_range(walk, range.c_str())) << GitError(); size_t res = 0; while (true) { git_oid oid; switch (git_revwalk_next(&oid, walk)) { case 0: ++res; break; case GIT_ITEROVER: return res; default: LOG(ERROR) << "git_revwalk_next: " << range << ": " << GitError(); throw Exception(); } } } size_t NumStashes(git_repository* repo) { size_t res = 0; auto* cb = +[](size_t index, const char* message, const git_oid* stash_id, void* payload) { ++*static_cast(payload); return 0; }; if (!git_stash_foreach(repo, cb, &res)) return res; // Example error: failed to parse signature - malformed e-mail. // See https://github.com/romkatv/powerlevel10k/issues/216. LOG(WARN) << "git_stash_foreach: " << GitError(); return 0; } git_reference* Head(git_repository* repo) { git_reference* symbolic = nullptr; switch (git_reference_lookup(&symbolic, repo, "HEAD")) { case 0: break; case GIT_ENOTFOUND: return nullptr; default: LOG(ERROR) << "git_reference_lookup: " << GitError(); throw Exception(); } git_reference* direct = nullptr; if (git_reference_resolve(&direct, symbolic)) { LOG(INFO) << "Empty git repo (no HEAD)"; return symbolic; } git_reference_free(symbolic); return direct; } const char* LocalBranchName(const git_reference* ref) { CHECK(ref); git_reference_t type = git_reference_type(ref); switch (type) { case GIT_REFERENCE_DIRECT: { return git_reference_is_branch(ref) ? git_reference_shorthand(ref) : ""; } case GIT_REFERENCE_SYMBOLIC: { static constexpr char kHeadPrefix[] = "refs/heads/"; const char* target = git_reference_symbolic_target(ref); if (!target) return ""; size_t len = std::strlen(target); if (len < sizeof(kHeadPrefix)) return ""; if (std::memcmp(target, kHeadPrefix, sizeof(kHeadPrefix) - 1)) return ""; return target + (sizeof(kHeadPrefix) - 1); } case GIT_REFERENCE_INVALID: case GIT_REFERENCE_ALL: break; } LOG(ERROR) << "Invalid reference type: " << type; throw Exception(); } RemotePtr GetRemote(git_repository* repo, const git_reference* local) { git_remote* remote; git_buf symref = {}; if (git_branch_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr; ON_SCOPE_EXIT(&) { git_remote_free(remote); git_buf_free(&symref); }; git_reference* ref; if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr; ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); }; const char* branch = nullptr; std::string name = remote ? git_remote_name(remote) : "."; if (git_branch_name(&branch, ref)) { branch = ""; } else if (remote) { VERIFY(std::strstr(branch, name.c_str()) == branch); VERIFY(branch[name.size()] == '/'); branch += name.size() + 1; } auto res = std::make_unique(); res->name = std::move(name); res->branch = branch; res->url = remote ? (git_remote_url(remote) ?: "") : ""; res->ref = std::exchange(ref, nullptr); return RemotePtr(res.release()); } PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local) { git_remote* remote; git_buf symref = {}; if (git_branch_push_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr; ON_SCOPE_EXIT(&) { git_remote_free(remote); git_buf_free(&symref); }; git_reference* ref; if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr; ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); }; std::string name = remote ? git_remote_name(remote) : "."; auto res = std::make_unique(); res->name = std::move(name); res->url = remote ? (git_remote_url(remote) ?: "") : ""; res->ref = std::exchange(ref, nullptr); return PushRemotePtr(res.release()); } } // namespace gitstatus