diff --git a/README.md b/README.md index fb92a0fb..b99ab1a6 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,16 @@ Get the target of the given reference. Returns the string target of the given reference. +### Repository.getRemoteHead([remoteName]) + +Get the branch that a remote's `HEAD` points to — i.e., the remote's default +branch. This is the equivalent of `git symbolic-ref refs/remotes//HEAD`. + +`remoteName` - The string name of the remote (default: `origin`). + +Returns the string reference name (e.g. `refs/remotes/origin/master`), or +`null` if the remote has no `HEAD` reference. + ### Repository.getShortHead() Get a possibly shortened version of value returns by `getHead()`. This will @@ -220,6 +230,16 @@ include ignored paths. Returns an integer status number if a path is specified and returns an object with path keys and integer status values if no path is specified. +### Repository.getSymbolicRefTarget(ref) + +Get the name of the reference that a symbolic reference points to, without +resolving it any further. This is the equivalent of `git symbolic-ref `. + +`ref` - The string reference. + +Returns the string name of the reference that `ref` points to, or `null` if +`ref` does not exist or is not a symbolic reference. + ### Repository.getUpstreamBranch([branch]) Get the upstream branch of the given branch. diff --git a/spec/fixtures/develop.git/HEAD b/spec/fixtures/develop.git/HEAD new file mode 100644 index 00000000..7ee7e4fe --- /dev/null +++ b/spec/fixtures/develop.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/develop diff --git a/spec/fixtures/develop.git/config b/spec/fixtures/develop.git/config new file mode 100644 index 00000000..b201f8e7 --- /dev/null +++ b/spec/fixtures/develop.git/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true +[branch "develop"] + remote = origin + merge = refs/heads/develop diff --git a/spec/fixtures/develop.git/index b/spec/fixtures/develop.git/index new file mode 100644 index 00000000..aca91fcb Binary files /dev/null and b/spec/fixtures/develop.git/index differ diff --git a/spec/fixtures/develop.git/objects/01/304958cafbf532dc719599cbf542855ec8c15f b/spec/fixtures/develop.git/objects/01/304958cafbf532dc719599cbf542855ec8c15f new file mode 100644 index 00000000..6fc9255a Binary files /dev/null and b/spec/fixtures/develop.git/objects/01/304958cafbf532dc719599cbf542855ec8c15f differ diff --git a/spec/fixtures/develop.git/objects/06/fcdd77c9348567c50638b30d406500f521c304 b/spec/fixtures/develop.git/objects/06/fcdd77c9348567c50638b30d406500f521c304 new file mode 100644 index 00000000..36a9e049 --- /dev/null +++ b/spec/fixtures/develop.git/objects/06/fcdd77c9348567c50638b30d406500f521c304 @@ -0,0 +1,2 @@ +xKOR02fH,*.QK*NMKA +m \ No newline at end of file diff --git a/spec/fixtures/develop.git/objects/08/fe2720d8e3fe3a5f81fbb289bc4c7a522f13da b/spec/fixtures/develop.git/objects/08/fe2720d8e3fe3a5f81fbb289bc4c7a522f13da new file mode 100644 index 00000000..5fa740fd Binary files /dev/null and b/spec/fixtures/develop.git/objects/08/fe2720d8e3fe3a5f81fbb289bc4c7a522f13da differ diff --git a/spec/fixtures/develop.git/objects/1e/f507d55f41fa587595afd2e165447dff9616ac b/spec/fixtures/develop.git/objects/1e/f507d55f41fa587595afd2e165447dff9616ac new file mode 100644 index 00000000..f143a977 Binary files /dev/null and b/spec/fixtures/develop.git/objects/1e/f507d55f41fa587595afd2e165447dff9616ac differ diff --git a/spec/fixtures/develop.git/objects/41/d3f5ab722c21457592142598d5d1e749927e61 b/spec/fixtures/develop.git/objects/41/d3f5ab722c21457592142598d5d1e749927e61 new file mode 100644 index 00000000..0ab79dad --- /dev/null +++ b/spec/fixtures/develop.git/objects/41/d3f5ab722c21457592142598d5d1e749927e61 @@ -0,0 +1 @@ +xmMj0) :h1J1U}YJo}ֲ>ciBG\gR_J9 \ No newline at end of file diff --git a/spec/fixtures/develop.git/refs/heads/develop b/spec/fixtures/develop.git/refs/heads/develop new file mode 100644 index 00000000..041d1e57 --- /dev/null +++ b/spec/fixtures/develop.git/refs/heads/develop @@ -0,0 +1 @@ +41d3f5ab722c21457592142598d5d1e749927e61 diff --git a/spec/fixtures/develop.git/refs/remotes/origin/HEAD b/spec/fixtures/develop.git/refs/remotes/origin/HEAD new file mode 100644 index 00000000..45cfece3 --- /dev/null +++ b/spec/fixtures/develop.git/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/develop diff --git a/spec/fixtures/develop.git/refs/remotes/origin/develop b/spec/fixtures/develop.git/refs/remotes/origin/develop new file mode 100644 index 00000000..041d1e57 --- /dev/null +++ b/spec/fixtures/develop.git/refs/remotes/origin/develop @@ -0,0 +1 @@ +41d3f5ab722c21457592142598d5d1e749927e61 diff --git a/spec/git-spec.js b/spec/git-spec.js index 23c4da93..9575a146 100644 --- a/spec/git-spec.js +++ b/spec/git-spec.js @@ -813,9 +813,6 @@ describe('git', () => { const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') fs.writeFileSync(filePath, 'first line is different', 'utf8') - let resolve; - let promise = new Promise(r => resolve = r) - await execCommands([`cd ${repoDirectory}`, 'git add a.txt']) diffs = repo.getLineDiffs('a.txt', 'first line is different', {useIndex: true}) @@ -1037,6 +1034,55 @@ describe('git', () => { }) }) + describe('.getSymbolicRefTarget(refName)', () => { + describe('when the reference is symbolic', () => { + it('returns the name of the reference it points to', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getSymbolicRefTarget('HEAD')).toBe('refs/heads/master') + + repo = git.open(path.join(__dirname, 'fixtures/references.git')) + expect(repo.getSymbolicRefTarget('refs/remotes/origin/HEAD')).toBe('refs/remotes/origin/master') + expect(repo.getSymbolicRefTarget('refs/remotes/upstream/HEAD')).toBe('refs/remotes/origin/master') + }) + }) + + describe('when the reference is direct (non-symbolic)', () => { + it('returns null', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getSymbolicRefTarget('refs/heads/master')).toBe(null) + }) + }) + + describe('when the reference does not exist', () => { + it('returns null', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getSymbolicRefTarget('refs/heads/nonexistent')).toBe(null) + }) + }) + }) + + describe('.getRemoteHead([remoteName])', () => { + let repo2 + beforeEach(() => { + repo = git.open(path.join(__dirname, 'fixtures/references.git')) + repo2 = git.open(path.join(__dirname, 'fixtures/develop.git')) + }) + + it('defaults to the "origin" remote', () => { + expect(repo.getRemoteHead()).toBe('refs/remotes/origin/master') + }) + + it("returns the branch that the given remote's HEAD points to", () => { + expect(repo.getRemoteHead('origin')).toBe('refs/remotes/origin/master') + expect(repo.getRemoteHead('upstream')).toBe('refs/remotes/origin/master') + expect(repo2.getRemoteHead('origin')).toBe('refs/remotes/origin/develop') + }) + + it('returns null when the remote has no HEAD reference', () => { + expect(repo.getRemoteHead('nonexistent')).toBe(null) + }) + }) + if (process.env.CI) { // NOTE: Testing a submodule requires that you be able to add one defined // at a path on disk. This is disallowed by default these days as a diff --git a/src/binding.cc b/src/binding.cc index 68632cec..8370d46a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -412,6 +412,30 @@ Napi::Value Repository::GetStatusForPath(const Napi::CallbackInfo& info) { } } +Napi::Value Repository::GetSymbolicRefTarget(const Napi::CallbackInfo& info) { + auto env = info.Env(); + Napi::HandleScope scope(env); + + if (info.Length() < 1) return env.Null(); + CHECK(info[0].IsString(), "Expected string as first argument", env); + + std::string refName(info[0].As()); + git_reference* ref = NULL; + + if (git_reference_lookup(&ref, GetRepository(info), refName.c_str()) != GIT_OK) { + return env.Null(); + } + + Napi::Value result = env.Null(); + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + const char* target = git_reference_symbolic_target(ref); + if (target) result = Napi::String::New(env, target); + } + + git_reference_free(ref); + return result; +} + Napi::Value Repository::CheckoutHead(const Napi::CallbackInfo& info) { auto env = info.Env(); Napi::HandleScope scope(env); @@ -1210,6 +1234,7 @@ void Repository::Init(Napi::Env env, Napi::Object exports) { InstanceMethod<&Repository::GetStatus>("getStatus", napi_default_method), InstanceMethod<&Repository::GetStatusForPath>("getStatusForPath", napi_default_method), InstanceMethod<&Repository::GetStatusAsync>("getStatusAsync", napi_default_method), + InstanceMethod<&Repository::GetSymbolicRefTarget>("getSymbolicRefTarget", napi_default_method), InstanceMethod<&Repository::CheckoutHead>("checkoutHead", napi_default_method), InstanceMethod<&Repository::GetReferenceTarget>("getReferenceTarget", napi_default_method), InstanceMethod<&Repository::GetDiffStats>("getDiffStats", napi_default_method), diff --git a/src/binding.hh b/src/binding.hh index 0b2591dc..d53235c7 100644 --- a/src/binding.hh +++ b/src/binding.hh @@ -48,6 +48,7 @@ private: Napi::Value GetStatus(const Napi::CallbackInfo& info); Napi::Value GetStatusAsync(const Napi::CallbackInfo& info); Napi::Value GetStatusForPath(const Napi::CallbackInfo& info); + Napi::Value GetSymbolicRefTarget(const Napi::CallbackInfo& info); Napi::Value CheckoutHead(const Napi::CallbackInfo& info); Napi::Value GetReferenceTarget(const Napi::CallbackInfo& info); Napi::Value GetDiffStats(const Napi::CallbackInfo& info); diff --git a/src/git.js b/src/git.js index c63c5159..361810bb 100644 --- a/src/git.js +++ b/src/git.js @@ -109,6 +109,10 @@ Repository.prototype.getUpstreamBranch = function (branch) { return `refs/remotes/${branchRemote}/${shortBranchMerge}` } +Repository.prototype.getRemoteHead = function (remoteName = 'origin') { + return this.getSymbolicRefTarget(`refs/remotes/${remoteName}/HEAD`) +} + Repository.prototype.getAheadBehindCount = function (branch = 'HEAD') { if (branch !== 'HEAD' && !branch.startsWith('refs/heads/')) { branch = `refs/heads/${branch}`