summaryrefslogtreecommitdiff
path: root/gitstatus/src/git.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gitstatus/src/git.cc')
-rw-r--r--gitstatus/src/git.cc242
1 files changed, 242 insertions, 0 deletions
diff --git a/gitstatus/src/git.cc b/gitstatus/src/git.cc
new file mode 100644
index 00000000..029b02bf
--- /dev/null
+++ b/gitstatus/src/git.cc
@@ -0,0 +1,242 @@
+// 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 <https://www.gnu.org/licenses/>.
+
+#include "git.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <utility>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#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<size_t*>(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<Remote>();
+ 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<PushRemote>();
+ res->name = std::move(name);
+ res->url = remote ? (git_remote_url(remote) ?: "") : "";
+ res->ref = std::exchange(ref, nullptr);
+ return PushRemotePtr(res.release());
+}
+
+} // namespace gitstatus