Skip to content

Commit 9f73e8b

Browse files
committed
remote: added methods to set and query the tracking branch status of normal heads, including test.
Config: SectionConstraint was updated with additional callable methods, the complete ConfigParser interface should be covered now Remote: refs methods is much more efficient now as it will set the search path to the directory containing the remote refs - previously it used the remotes/ base directory and pruned the search result
1 parent af5abca commit 9f73e8b

File tree

4 files changed

+103
-8
lines changed

4 files changed

+103
-8
lines changed

lib/git/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ class SectionConstraint(object):
7474
7575
It supports all ConfigParser methods that operate on an option"""
7676
__slots__ = ("_config", "_section_name")
77-
_valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option")
77+
_valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
78+
"remove_section", "remove_option", "options")
7879

7980
def __init__(self, config, section):
8081
self._config = config

lib/git/refs.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
hex_to_bin
3030
)
3131

32+
from config import (
33+
GitConfigParser,
34+
SectionConstraint
35+
)
36+
3237
from exc import GitCommandError
3338

3439
__all__ = ("SymbolicReference", "Reference", "HEAD", "Head", "TagReference",
@@ -701,6 +706,8 @@ class Head(Reference):
701706
>>> head.commit.hexsha
702707
'1c09f116cbc2cb4100fb6935bb162daa4723f455'"""
703708
_common_path_default = "refs/heads"
709+
k_config_remote = "remote"
710+
k_config_remote_ref = "merge" # branch to merge from remote
704711

705712
@classmethod
706713
def create(cls, repo, path, commit='HEAD', force=False, **kwargs):
@@ -747,6 +754,44 @@ def delete(cls, repo, *heads, **kwargs):
747754
flag = "-D"
748755
repo.git.branch(flag, *heads)
749756

757+
758+
def set_tracking_branch(self, remote_reference):
759+
"""Configure this branch to track the given remote reference. This will alter
760+
this branch's configuration accordingly.
761+
:param remote_reference: The remote reference to track or None to untrack
762+
any references
763+
:return: self"""
764+
if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
765+
raise ValueError("Incorrect parameter type: %r" % remote_reference)
766+
# END handle type
767+
768+
writer = self.config_writer()
769+
if remote_reference is None:
770+
writer.remove_option(self.k_config_remote)
771+
writer.remove_option(self.k_config_remote_ref)
772+
if len(writer.options()) == 0:
773+
writer.remove_section()
774+
# END handle remove section
775+
else:
776+
writer.set_value(self.k_config_remote, remote_reference.remote_name)
777+
writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head))
778+
# END handle ref value
779+
780+
return self
781+
782+
783+
def tracking_branch(self):
784+
""":return: The remote_reference we are tracking, or None if we are
785+
not a tracking branch"""
786+
reader = self.config_reader()
787+
if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
788+
ref = Head(self.repo, Head.to_full_path(reader.get_value(self.k_config_remote_ref)))
789+
remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
790+
return RemoteReference(self.repo, remote_refpath)
791+
# END handle have tracking branch
792+
793+
# we are not a tracking branch
794+
return None
750795

751796
def rename(self, new_path, force=False):
752797
"""Rename self to a new path
@@ -800,6 +845,29 @@ def checkout(self, force=False, **kwargs):
800845
self.repo.git.checkout(self, **kwargs)
801846
return self.repo.active_branch
802847

848+
#{ Configruation
849+
850+
def _config_parser(self, read_only):
851+
if read_only:
852+
parser = self.repo.config_reader()
853+
else:
854+
parser = self.repo.config_writer()
855+
# END handle parser instance
856+
857+
return SectionConstraint(parser, 'branch "%s"' % self.name)
858+
859+
def config_reader(self):
860+
""":return: A configuration parser instance constrained to only read
861+
this instance's values"""
862+
return self._config_parser(read_only=True)
863+
864+
def config_writer(self):
865+
""":return: A configuration writer instance with read-and write acccess
866+
to options of this head"""
867+
return self._config_parser(read_only=False)
868+
869+
#} END configuration
870+
803871

804872
class TagReference(Reference):
805873
"""Class representing a lightweight tag reference which either points to a commit
@@ -893,6 +961,16 @@ class RemoteReference(Head):
893961
"""Represents a reference pointing to a remote head."""
894962
_common_path_default = "refs/remotes"
895963

964+
965+
@classmethod
966+
def iter_items(cls, repo, common_path = None, remote=None):
967+
"""Iterate remote references, and if given, constrain them to the given remote"""
968+
common_path = common_path or cls._common_path_default
969+
if remote is not None:
970+
common_path = join_path(common_path, str(remote))
971+
# END handle remote constraint
972+
return super(RemoteReference, cls).iter_items(repo, common_path)
973+
896974
@property
897975
def remote_name(self):
898976
"""

lib/git/remote.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,11 +468,7 @@ def refs(self):
468468
you to omit the remote path portion, i.e.::
469469
remote.refs.master # yields RemoteReference('/refs/remotes/origin/master')"""
470470
out_refs = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
471-
for ref in RemoteReference.list_items(self.repo):
472-
if ref.remote_name == self.name:
473-
out_refs.append(ref)
474-
# END if names match
475-
# END for each ref
471+
out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
476472
assert out_refs, "Remote %s did not have any references" % self.name
477473
return out_refs
478474

test/git/test_refs.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,33 @@ def test_tags(self):
6363
assert len(s) == ref_count
6464
assert len(s|s) == ref_count
6565

66-
def test_heads(self):
67-
for head in self.rorepo.heads:
66+
@with_rw_repo('HEAD', bare=False)
67+
def test_heads(self, rwrepo):
68+
for head in rwrepo.heads:
6869
assert head.name
6970
assert head.path
7071
assert "refs/heads" in head.path
7172
prev_object = head.object
7273
cur_object = head.object
7374
assert prev_object == cur_object # represent the same git object
7475
assert prev_object is not cur_object # but are different instances
76+
77+
writer = head.config_writer()
78+
tv = "testopt"
79+
writer.set_value(tv, 1)
80+
assert writer.get_value(tv) == 1
81+
del(writer)
82+
assert head.config_reader().get_value(tv) == 1
83+
head.config_writer().remove_option(tv)
84+
85+
# after the clone, we might still have a tracking branch setup
86+
head.set_tracking_branch(None)
87+
assert head.tracking_branch() is None
88+
remote_ref = rwrepo.remotes[0].refs[0]
89+
assert head.set_tracking_branch(remote_ref) is head
90+
assert head.tracking_branch() == remote_ref
91+
head.set_tracking_branch(None)
92+
assert head.tracking_branch() is None
7593
# END for each head
7694

7795
def test_refs(self):
@@ -208,6 +226,8 @@ def test_head_reset(self, rw_repo):
208226
refs = remote.refs
209227
RemoteReference.delete(rw_repo, *refs)
210228
remote_refs_so_far += len(refs)
229+
for ref in refs:
230+
assert ref.remote_name == remote.name
211231
# END for each ref to delete
212232
assert remote_refs_so_far
213233

0 commit comments

Comments
 (0)