Skip to content

Commit 3acfe90

Browse files
telamonianfcollonval
authored andcommitted
Fix #432 - improve GitPanel refresh by streamlining Git.branch (#435)
* fixes #432: speed up branch handler by greatly reducing subprocess calls uses `git for-each-ref` in place of `git show-ref` * updated unittests for the new version of Git.branch * cleanup; added GitBranchHandler unittest
1 parent 9c7d454 commit 3acfe90

File tree

3 files changed

+268
-230
lines changed

3 files changed

+268
-230
lines changed

jupyterlab_git/git.py

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -364,74 +364,116 @@ def diff(self, top_repo_path):
364364

365365
def branch(self, current_path):
366366
"""
367-
Execute 'git show-ref' command & return the result.
367+
Execute 'git for-each-ref' command & return the result.
368368
"""
369+
heads = self.branch_heads(current_path)
370+
if heads["code"] != 0:
371+
# error; bail
372+
return heads
373+
374+
remotes = self.branch_remotes(current_path)
375+
if remotes["code"] != 0:
376+
# error; bail
377+
return remotes
378+
379+
# all's good; concatenate results and return
380+
return {"code": 0, "branches": heads["branches"] + remotes["branches"]}
381+
382+
def branch_heads(self, current_path):
383+
"""
384+
Execute 'git for-each-ref' command on refs/heads & return the result.
385+
"""
386+
formats = ['refname:short', 'objectname', 'upstream:short', 'HEAD']
387+
cmd = ["git", "for-each-ref", "--format=" + "%09".join("%({})".format(f) for f in formats), "refs/heads/"]
369388
p = subprocess.Popen(
370-
["git", "show-ref"],
389+
cmd,
371390
stdout=PIPE,
372391
stderr=PIPE,
373392
cwd=os.path.join(self.root_dir, current_path),
374393
)
375394
output, error = p.communicate()
376395
if p.returncode == 0:
396+
current_branch_seen = False
377397
results = []
378398
try:
379-
current_branch = self.get_current_branch(current_path)
380-
for line in output.decode("utf-8").splitlines():
381-
# The format for git show-ref is '<SHA-1 ID> <space> <reference name>'
382-
# For this method we are only interested in reference name.
383-
# Reference : https://git-scm.com/docs/git-show-ref#_output
384-
commit_sha = line.strip().split()[0].strip()
385-
reference_name = line.strip().split()[1].strip()
386-
if self._is_branch(reference_name):
387-
branch_name = self._get_branch_name(reference_name)
388-
is_current_branch = self._is_current_branch(
389-
branch_name, current_branch
390-
)
391-
is_remote_branch = self._is_remote_branch(reference_name)
392-
upstream_branch_name = None
393-
if not is_remote_branch:
394-
upstream_branch_name = self.get_upstream_branch(
395-
current_path, branch_name
396-
)
397-
tag = self._get_tag(current_path, commit_sha)
398-
results.append(
399-
{
400-
"is_current_branch": is_current_branch,
401-
"is_remote_branch": is_remote_branch,
402-
"name": branch_name,
403-
"upstream": upstream_branch_name,
404-
"top_commit": commit_sha,
405-
"tag": tag,
406-
}
407-
)
399+
for name,commit_sha,upstream_name,is_current_branch in (line.split('\t') for line in output.decode("utf-8").splitlines()):
400+
# Format reference : https://git-scm.com/docs/git-for-each-ref#_field_names
401+
is_current_branch = bool(is_current_branch.strip())
402+
current_branch_seen |= is_current_branch
403+
404+
results.append({
405+
"is_current_branch": is_current_branch,
406+
"is_remote_branch": False,
407+
"name": name,
408+
"upstream": upstream_name if upstream_name else None,
409+
"top_commit": commit_sha,
410+
"tag": None,
411+
})
408412

409413
# Remote branch is seleted use 'git branch -a' as fallback machanism
410414
# to get add detached head on remote branch to preserve older functionality
411415
# TODO : Revisit this to checkout new local branch with same name as remote
412416
# when the remote branch is seleted, VS Code git does the same thing.
413-
if current_branch == "HEAD":
414-
results.append(
415-
{
416-
"is_current_branch": True,
417-
"is_remote_branch": False,
418-
"name": self._get_detached_head_name(current_path),
419-
"upstream": None,
420-
"top_commit": None,
421-
"tag": None,
422-
}
423-
)
417+
if not current_branch_seen and self.get_current_branch(current_path) == "HEAD":
418+
results.append({
419+
"is_current_branch": True,
420+
"is_remote_branch": False,
421+
"name": self._get_detached_head_name(current_path),
422+
"upstream": None,
423+
"top_commit": None,
424+
"tag": None,
425+
})
426+
return {"code": p.returncode, "branches": results}
427+
except Exception as downstream_error:
428+
return {
429+
"code": -1,
430+
"command": ' '.join(cmd),
431+
"message": str(downstream_error),
432+
}
433+
else:
434+
return {
435+
"code": p.returncode,
436+
"command": ' '.join(cmd),
437+
"message": error.decode("utf-8"),
438+
}
439+
440+
def branch_remotes(self, current_path):
441+
"""
442+
Execute 'git for-each-ref' command on refs/heads & return the result.
443+
"""
444+
formats = ['refname:short', 'objectname']
445+
cmd = ["git", "for-each-ref", "--format=" + "%09".join("%({})".format(f) for f in formats), "refs/remotes/"]
446+
p = subprocess.Popen(
447+
cmd,
448+
stdout=PIPE,
449+
stderr=PIPE,
450+
cwd=os.path.join(self.root_dir, current_path),
451+
)
452+
output, error = p.communicate()
453+
if p.returncode == 0:
454+
results = []
455+
try:
456+
for name,commit_sha in (line.split('\t') for line in output.decode("utf-8").splitlines()):
457+
# Format reference : https://git-scm.com/docs/git-for-each-ref#_field_names
458+
results.append({
459+
"is_current_branch": False,
460+
"is_remote_branch": True,
461+
"name": name,
462+
"upstream": None,
463+
"top_commit": commit_sha,
464+
"tag": None,
465+
})
424466
return {"code": p.returncode, "branches": results}
425467
except Exception as downstream_error:
426468
return {
427-
"code": p.returncode,
428-
"command": "git show-ref",
469+
"code": -1,
470+
"command": ' '.join(cmd),
429471
"message": str(downstream_error),
430472
}
431473
else:
432474
return {
433475
"code": p.returncode,
434-
"command": "git show-ref",
476+
"command": ' '.join(cmd),
435477
"message": error.decode("utf-8"),
436478
}
437479

0 commit comments

Comments
 (0)