diff --git a/native/v8_extensions/git.mm b/native/v8_extensions/git.mm index 3a8c13885..70a58069e 100644 --- a/native/v8_extensions/git.mm +++ b/native/v8_extensions/git.mm @@ -1,6 +1,7 @@ #import "git.h" #import "include/git2.h" #import +#import namespace v8_extensions { @@ -74,6 +75,106 @@ namespace v8_extensions { return v8Statuses; } + int getCommitCount(const git_oid* fromCommit, const git_oid* toCommit) { + int count = 0; + git_revwalk *revWalk; + if (git_revwalk_new(&revWalk, repo) == GIT_OK) { + git_revwalk_push(revWalk, fromCommit); + git_revwalk_hide(revWalk, toCommit); + git_oid currentCommit; + while (git_revwalk_next(¤tCommit, revWalk) == GIT_OK) + count++; + git_revwalk_free(revWalk); + } + return count; + } + + void getShortBranchName(const char** out, const char* branchName) { + *out = NULL; + if (branchName == NULL) + return; + int branchNameLength = strlen(branchName); + if (branchNameLength < 12) + return; + if (strncmp("refs/heads/", branchName, 11) != 0) + return; + + int shortNameLength = branchNameLength - 11; + char* shortName = (char*) malloc(sizeof(char) * shortNameLength + 1); + shortName[shortNameLength] = '\0'; + strncpy(shortName, &branchName[11], shortNameLength); + *out = shortName; + } + + void getUpstreamBranch(const char** out, git_reference *branch) { + *out = NULL; + git_config *config; + if (git_repository_config(&config, repo) != GIT_OK) + return; + + const char* branchName = git_reference_name(branch); + const char* shortBranchName; + getShortBranchName(&shortBranchName, branchName); + if (shortBranchName == NULL) + return; + + int shortBranchNameLength = strlen(shortBranchName); + char* remoteKey = (char*) malloc(sizeof(char) * shortBranchNameLength + 15); + remoteKey[shortBranchNameLength + 14] = '\0'; + sprintf(remoteKey, "branch.%s.remote", shortBranchName); + char* mergeKey = (char*) malloc(sizeof(char) * shortBranchNameLength + 14); + mergeKey[shortBranchNameLength + 13] = '\0'; + sprintf(mergeKey, "branch.%s.merge", shortBranchName); + free((char*)shortBranchName); + + const char *remote; + const char *merge; + if (git_config_get_string(&remote, config, remoteKey) == GIT_OK + && git_config_get_string(&merge, config, mergeKey) == GIT_OK) { + const char* shortMergeBranchName; + getShortBranchName(&shortMergeBranchName, merge); + if (shortMergeBranchName != NULL) { + int shortMergeBranchNameLength = strlen(shortMergeBranchName); + int updateRefLength = strlen(remote) + shortMergeBranchNameLength + 15; + char* upstreamBranch = (char*) malloc(sizeof(char) * updateRefLength); + sprintf(upstreamBranch, "refs/remotes/%s/%s", remote, shortMergeBranchName); + free((char*)shortMergeBranchName); + *out = upstreamBranch; + } + } + + free(remoteKey); + free(mergeKey); + git_config_free(config); + } + + CefRefPtr GetAheadBehindCounts() { + CefRefPtr result = CefV8Value::CreateObject(NULL); + git_reference *head; + if (git_repository_head(&head, repo) == GIT_OK) { + const char* upstreamBranchName; + getUpstreamBranch(&upstreamBranchName, head); + if (upstreamBranchName != NULL) { + git_reference *upstream; + if (git_reference_lookup(&upstream, repo, upstreamBranchName) == GIT_OK) { + const git_oid* headSha = git_reference_target(head); + const git_oid* upstreamSha = git_reference_target(upstream); + git_oid mergeBase; + if (git_merge_base(&mergeBase, repo, headSha, upstreamSha) == GIT_OK) { + int ahead = getCommitCount(headSha, &mergeBase); + result->SetValue("ahead", CefV8Value::CreateInt(ahead), V8_PROPERTY_ATTRIBUTE_NONE); + int behind = getCommitCount(upstreamSha, &mergeBase); + result->SetValue("behind", CefV8Value::CreateInt(behind), V8_PROPERTY_ATTRIBUTE_NONE); + } + git_reference_free(upstream); + } + free((char*)upstreamBranchName); + } + git_reference_free(head); + } + return result; + } + CefRefPtr IsIgnored(const char *path) { int ignored; if (git_ignore_path_is_ignored(&ignored, repo, path) == GIT_OK) { @@ -209,7 +310,8 @@ namespace v8_extensions { void Git::CreateContextBinding(CefRefPtr context) { const char* methodNames[] = { "getRepository", "getHead", "getPath", "isIgnored", "getStatus", "checkoutHead", - "getDiffStats", "isSubmodule", "refreshIndex", "destroy", "getStatuses" + "getDiffStats", "isSubmodule", "refreshIndex", "destroy", "getStatuses", + "getAheadBehindCounts" }; CefRefPtr nativeObject = CefV8Value::CreateObject(NULL); @@ -302,6 +404,12 @@ namespace v8_extensions { return true; } + if (name == "getAheadBehindCounts") { + GitRepository *userData = (GitRepository *)object->GetUserData().get(); + retval = userData->GetAheadBehindCounts(); + return true; + } + return false; } } diff --git a/src/app/git.coffee b/src/app/git.coffee index f065c209b..e8fa7463b 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -138,5 +138,8 @@ class Git directoryStatus |= status if path.indexOf(directoryPath) is 0 directoryStatus + getAheadBehindCounts: -> + @getRepo().getAheadBehindCounts() + _.extend Git.prototype, Subscriber _.extend Git.prototype, EventEmitter diff --git a/src/stdlib/git-repository.coffee b/src/stdlib/git-repository.coffee index 2386ba868..b842630cc 100644 --- a/src/stdlib/git-repository.coffee +++ b/src/stdlib/git-repository.coffee @@ -17,3 +17,4 @@ class GitRepository isSubmodule: $git.isSubmodule refreshIndex: $git.refreshIndex destroy: $git.destroy + getAheadBehindCounts: $git.getAheadBehindCounts