13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from bzrlib import urlutils
19
from bzrlib.bzrdir import BzrDir, BzrDirFormat
20
from bzrlib.errors import NoSuchFile, NotLocalUrl
21
from bzrlib.lockable_files import TransportLock
22
from bzrlib.repository import Repository
23
from bzrlib.trace import info
24
from bzrlib.transport import Transport
26
from bzrlib.plugins.git import git
27
from bzrlib.plugins.git.branch import GitBranch
28
from bzrlib.plugins.git.dir import GitDir
29
from bzrlib.plugins.git.foreign import ForeignBranch
30
from bzrlib.plugins.git.repository import GitFormat, GitRepository
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Remote dirs, repositories and branches."""
19
from __future__ import absolute_import
21
from io import BytesIO
36
from ...errors import (
48
UninitializableFormat,
50
from ...revisiontree import RevisionTree
51
from ...sixish import text_type
52
from ...transport import (
54
register_urlparse_netloc_protocol,
59
user_agent_for_github,
73
BareLocalGitControlDirFormat,
76
GitSmartRemoteNotSupported,
79
from .mapping import (
82
from .object_store import (
88
from .repository import (
100
from dulwich.errors import (
103
from dulwich.pack import (
105
pack_objects_to_data,
107
from dulwich.protocol import ZERO_SHA
108
from dulwich.refs import (
112
from dulwich.repo import (
35
from dulwich.pack import PackData
121
import urllib.parse as urlparse
122
from urllib.parse import splituser, splitnport
125
from urllib import splituser, splitnport
127
# urlparse only supports a limited number of schemes by default
128
register_urlparse_netloc_protocol('git')
129
register_urlparse_netloc_protocol('git+ssh')
131
from dulwich.pack import load_pack_index
134
class GitPushResult(PushResult):
136
def _lookup_revno(self, revid):
138
return _quick_lookup_revno(self.source_branch, self.target_branch,
140
except GitSmartRemoteNotSupported:
145
return self._lookup_revno(self.old_revid)
149
return self._lookup_revno(self.new_revid)
152
# Don't run any tests on GitSmartTransport as it is not intended to be
153
# a full implementation of Transport
154
def get_test_permutations():
158
def split_git_url(url):
162
:return: Tuple with host, port, username, path.
164
(scheme, netloc, loc, _, _) = urlparse.urlsplit(url)
165
path = urlparse.unquote(loc)
166
if path.startswith("/~"):
168
(username, hostport) = splituser(netloc)
169
(host, port) = splitnport(hostport, None)
170
return (host, port, username, path)
173
class RemoteGitError(BzrError):
175
_fmt = "Remote server error: %(msg)s"
178
def parse_git_error(url, message):
179
"""Parse a remote git server error and return a bzr exception.
181
:param url: URL of the remote repository
182
:param message: Message sent by the remote git server
184
message = str(message).strip()
185
if message.startswith("Could not find Repository "):
186
return NotBranchError(url, message)
187
if message == "HEAD failed to update":
188
base_url, _ = urlutils.split_segment_parameters(url)
190
("Unable to update remote HEAD branch. To update the master "
191
"branch, specify the URL %s,branch=master.") % base_url)
192
# Don't know, just return it to the user as-is
193
return RemoteGitError(message)
38
196
class GitSmartTransport(Transport):
40
198
def __init__(self, url, _client=None):
41
199
Transport.__init__(self, url)
42
(scheme, _, loc, _, _) = urlparse.urlsplit(url)
43
assert scheme == "git"
44
hostport, self._path = urllib.splithost(loc)
45
(self._host, self._port) = urllib.splitnport(hostport, git.protocol.TCP_GIT_PORT)
46
if _client is not None:
47
self._client = _client
49
self._client = git.client.TCPGitClient(self._host, self._port)
51
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
54
info("git: %s" % text)
55
self._client.fetch_pack(self._path, determine_wants, graph_walker,
58
def fetch_objects(self, determine_wants, graph_walker, progress=None):
59
fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
60
self.fetch_pack(determine_wants, graph_walker, lambda x: os.write(fd, x), progress)
64
for o in p.iterobjects():
200
(self._host, self._port, self._username, self._path) = \
202
if 'transport' in debug.debug_flags:
203
trace.mutter('host: %r, user: %r, port: %r, path: %r',
204
self._host, self._username, self._port, self._path)
205
self._client = _client
206
self._stripped_path = self._path.rsplit(",", 1)[0]
208
def external_url(self):
211
def has(self, relpath):
214
def _get_client(self):
215
raise NotImplementedError(self._get_client)
218
return self._stripped_path
69
220
def get(self, path):
70
221
raise NoSuchFile(path)
223
def abspath(self, relpath):
224
return urlutils.join(self.base, relpath)
72
226
def clone(self, offset=None):
73
227
"""See Transport.clone()."""
74
228
if offset is None:
77
231
newurl = urlutils.join(self.base, offset)
79
return GitSmartTransport(newurl, self._client)
233
return self.__class__(newurl, self._client)
236
class TCPGitSmartTransport(GitSmartTransport):
240
def _get_client(self):
241
if self._client is not None:
246
# return dulwich.client.LocalGitClient()
247
return dulwich.client.SubprocessGitClient()
248
return dulwich.client.TCPGitClient(self._host, self._port,
249
report_activity=self._report_activity)
252
class SSHSocketWrapper(object):
254
def __init__(self, sock):
257
def read(self, len=None):
258
return self.sock.recv(len)
260
def write(self, data):
261
return self.sock.write(data)
264
return len(select.select([self.sock.fileno()], [], [], 0)[0]) > 0
267
class DulwichSSHVendor(dulwich.client.SSHVendor):
270
from ...transport import ssh
271
self.bzr_ssh_vendor = ssh._get_ssh_vendor()
273
def run_command(self, host, command, username=None, port=None):
274
connection = self.bzr_ssh_vendor.connect_ssh(username=username,
275
password=None, port=port, host=host, command=command)
276
(kind, io_object) = connection.get_sock_or_pipes()
278
return SSHSocketWrapper(io_object)
280
raise AssertionError("Unknown io object kind %r'" % kind)
283
#dulwich.client.get_ssh_vendor = DulwichSSHVendor
286
class SSHGitSmartTransport(GitSmartTransport):
291
path = self._stripped_path
292
if path.startswith("/~/"):
296
def _get_client(self):
297
if self._client is not None:
301
location_config = config.LocationConfig(self.base)
302
client = dulwich.client.SSHGitClient(self._host, self._port, self._username,
303
report_activity=self._report_activity)
304
# Set up alternate pack program paths
305
upload_pack = location_config.get_user_option('git_upload_pack')
307
client.alternative_paths["upload-pack"] = upload_pack
308
receive_pack = location_config.get_user_option('git_receive_pack')
310
client.alternative_paths["receive-pack"] = receive_pack
314
class RemoteGitBranchFormat(GitBranchFormat):
316
def get_format_description(self):
317
return 'Remote Git Branch'
320
def _matchingcontroldir(self):
321
return RemoteGitControlDirFormat()
323
def initialize(self, a_controldir, name=None, repository=None,
324
append_revisions_only=None):
325
raise UninitializableFormat(self)
328
class DefaultProgressReporter(object):
330
_GIT_PROGRESS_PARTIAL_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
331
_GIT_PROGRESS_TOTAL_RE = re.compile(r"(.*?): (\d+)")
333
def __init__(self, pb):
336
def progress(self, text):
337
text = text.rstrip(b"\r\n")
338
text = text.decode('utf-8')
339
if text.startswith('error: '):
340
trace.show_error('git: %s', text[len(b'error: '):])
342
trace.mutter("git: %s", text)
343
g = self._GIT_PROGRESS_PARTIAL_RE.match(text)
345
(text, pct, current, total) = g.groups()
346
self.pb.update(text, int(current), int(total))
348
g = self._GIT_PROGRESS_TOTAL_RE.match(text)
350
(text, total) = g.groups()
351
self.pb.update(text, None, int(total))
353
trace.note("%s", text)
82
356
class RemoteGitDir(GitDir):
84
def __init__(self, transport, lockfiles, format):
358
def __init__(self, transport, format, client, client_path):
85
359
self._format = format
86
360
self.root_transport = transport
87
361
self.transport = transport
88
self._lockfiles = lockfiles
362
self._mode_check_done = None
363
self._client = client
364
self._client_path = client_path
365
self.base = self.root_transport.base
369
def _gitrepository_class(self):
370
return RemoteGitRepository
372
def archive(self, format, committish, write_data, progress=None, write_error=None,
373
subdirs=None, prefix=None):
374
if format not in ('tar', 'zip'):
375
raise errors.NoSuchExportFormat(format)
377
pb = ui.ui_factory.nested_progress_bar()
378
progress = DefaultProgressReporter(pb).progress
382
self._client.archive(self._client_path, committish,
383
write_data, progress, write_error, format=format,
384
subdirs=subdirs, prefix=prefix)
385
except GitProtocolError as e:
386
raise parse_git_error(self.transport.external_url(), e)
391
def fetch_pack(self, determine_wants, graph_walker, pack_data, progress=None):
393
pb = ui.ui_factory.nested_progress_bar()
394
progress = DefaultProgressReporter(pb).progress
398
result = self._client.fetch_pack(self._client_path, determine_wants,
399
graph_walker, pack_data, progress)
400
if result.refs is None:
402
self._refs = remote_refs_dict_to_container(result.refs, result.symrefs)
404
except GitProtocolError as e:
405
raise parse_git_error(self.transport.external_url(), e)
410
def send_pack(self, get_changed_refs, generate_pack_data, progress=None):
412
pb = ui.ui_factory.nested_progress_bar()
413
progress = DefaultProgressReporter(pb).progress
416
def get_changed_refs_wrapper(refs):
417
# TODO(jelmer): This drops symref information
418
self._refs = remote_refs_dict_to_container(refs)
419
return get_changed_refs(refs)
421
return self._client.send_pack(self._client_path,
422
get_changed_refs_wrapper, generate_pack_data, progress)
423
except GitProtocolError as e:
424
raise parse_git_error(self.transport.external_url(), e)
429
def create_branch(self, name=None, repository=None,
430
append_revisions_only=None, ref=None):
431
refname = self._get_selected_ref(name, ref)
432
if refname != b'HEAD' and refname in self.get_refs_container():
433
raise AlreadyBranchError(self.user_url)
434
if refname in self.get_refs_container():
435
ref_chain, unused_sha = self.get_refs_container().follow(self._get_selected_ref(None))
436
if ref_chain[0] == b'HEAD':
437
refname = ref_chain[1]
438
repo = self.open_repository()
439
return RemoteGitBranch(self, repo, refname)
441
def destroy_branch(self, name=None):
442
refname = self._get_selected_ref(name)
443
def get_changed_refs(old_refs):
445
if not refname in ret:
446
raise NotBranchError(self.user_url)
447
ret[refname] = dulwich.client.ZERO_SHA
449
def generate_pack_data(have, want, ofs_delta=False):
450
return pack_objects_to_data([])
451
self.send_pack(get_changed_refs, generate_pack_data)
455
return self.control_url
458
def user_transport(self):
459
return self.root_transport
462
def control_url(self):
463
return self.control_transport.base
466
def control_transport(self):
467
return self.root_transport
90
469
def open_repository(self):
91
return RemoteGitRepository(self, self._lockfiles)
470
return RemoteGitRepository(self)
93
def open_branch(self):
472
def open_branch(self, name=None, unsupported=False,
473
ignore_fallbacks=False, ref=None, possible_transports=None,
94
475
repo = self.open_repository()
95
# TODO: Support for multiple branches in one bzrdir in bzrlib!
96
return RemoteGitBranch(self, repo, "HEAD", self._lockfiles)
476
ref = self._get_selected_ref(name, ref)
477
if not nascent_ok and ref not in self.get_refs_container():
478
raise NotBranchError(self.root_transport.base,
480
ref_chain, unused_sha = self.get_refs_container().follow(ref)
481
return RemoteGitBranch(self, repo, ref_chain[-1])
98
def open_workingtree(self):
483
def open_workingtree(self, recommend_upgrade=False):
99
484
raise NotLocalUrl(self.transport.base)
486
def has_workingtree(self):
489
def get_peeled(self, name):
490
return self.get_refs_container().get_peeled(name)
492
def get_refs_container(self):
493
if self._refs is not None:
495
result = self.fetch_pack(lambda x: None, None,
496
lambda x: None, lambda x: trace.mutter("git: %s" % x))
497
self._refs = remote_refs_dict_to_container(
498
result.refs, result.symrefs)
501
def push_branch(self, source, revision_id=None, overwrite=False,
502
remember=False, create_prefix=False, lossy=False,
504
"""Push the source branch into this ControlDir."""
505
if revision_id is None:
506
# No revision supplied by the user, default to the branch
508
revision_id = source.last_revision()
510
push_result = GitPushResult()
511
push_result.workingtree_updated = None
512
push_result.master_branch = None
513
push_result.source_branch = source
514
push_result.stacked_on = None
515
push_result.branch_push_result = None
516
repo = self.find_repository()
517
refname = self._get_selected_ref(name)
518
if isinstance(source, GitBranch) and lossy:
519
raise errors.LossyPushToSameVCS(source.controldir, self)
520
source_store = get_object_store(source.repository)
521
with source_store.lock_read():
522
def get_changed_refs(refs):
523
self._refs = remote_refs_dict_to_container(refs)
525
# TODO(jelmer): Unpeel if necessary
526
push_result.new_original_revid = revision_id
528
new_sha = source_store._lookup_revision_sha1(revision_id)
530
new_sha = repo.lookup_bzr_revision_id(revision_id)[0]
532
if remote_divergence(ret.get(refname), new_sha, source_store):
533
raise DivergedBranches(
534
source, self.open_branch(name, nascent_ok=True))
535
ret[refname] = new_sha
538
generate_pack_data = source_store.generate_lossy_pack_data
540
generate_pack_data = source_store.generate_pack_data
541
new_refs = self.send_pack(get_changed_refs, generate_pack_data)
542
push_result.new_revid = repo.lookup_foreign_revision_id(
545
old_remote = self._refs[refname]
547
old_remote = ZERO_SHA
548
push_result.old_revid = repo.lookup_foreign_revision_id(old_remote)
549
self._refs = remote_refs_dict_to_container(new_refs)
550
push_result.target_branch = self.open_branch(name)
551
if old_remote != ZERO_SHA:
552
push_result.branch_push_result = GitBranchPushResult()
553
push_result.branch_push_result.source_branch = source
554
push_result.branch_push_result.target_branch = push_result.target_branch
555
push_result.branch_push_result.local_branch = None
556
push_result.branch_push_result.master_branch = push_result.target_branch
557
push_result.branch_push_result.old_revid = push_result.old_revid
558
push_result.branch_push_result.new_revid = push_result.new_revid
559
push_result.branch_push_result.new_original_revid = push_result.new_original_revid
560
if source.get_push_location() is None or remember:
561
source.set_push_location(push_result.target_branch.base)
564
def _find_commondir(self):
565
# There is no way to find the commondir, if there is any.
569
class EmptyObjectStoreIterator(dict):
571
def iterobjects(self):
575
class TemporaryPackIterator(Pack):
577
def __init__(self, path, resolve_ext_ref):
578
super(TemporaryPackIterator, self).__init__(
579
path, resolve_ext_ref=resolve_ext_ref)
580
self._idx_load = lambda: self._idx_load_or_generate(self._idx_path)
582
def _idx_load_or_generate(self, path):
583
if not os.path.exists(path):
584
pb = ui.ui_factory.nested_progress_bar()
586
def report_progress(cur, total):
587
pb.update("generating index", cur, total)
588
self.data.create_index(path,
589
progress=report_progress)
592
return load_pack_index(path)
595
if self._idx is not None:
597
os.remove(self._idx_path)
598
if self._data is not None:
600
os.remove(self._data_path)
603
class BzrGitHttpClient(dulwich.client.HttpGitClient):
605
def __init__(self, transport, *args, **kwargs):
606
self.transport = transport
607
super(BzrGitHttpClient, self).__init__(transport.external_url(), *args, **kwargs)
609
def _http_request(self, url, headers=None, data=None,
610
allow_compression=False):
611
"""Perform HTTP request.
613
:param url: Request URL.
614
:param headers: Optional custom headers to override defaults.
615
:param data: Request data.
616
:param allow_compression: Allow GZipped communication.
617
:return: Tuple (`response`, `read`), where response is an `urllib3`
618
response object with additional `content_type` and
619
`redirect_location` properties, and `read` is a consumable read
620
method for the response data.
622
from breezy.transport.http._urllib2_wrappers import Request
623
headers['User-agent'] = user_agent_for_github()
624
headers["Pragma"] = "no-cache"
625
if allow_compression:
626
headers["Accept-Encoding"] = "gzip"
628
headers["Accept-Encoding"] = "identity"
631
('GET' if data is None else 'POST'),
633
accepted_errors=[200, 404])
635
response = self.transport._perform(request)
637
if response.code == 404:
638
raise NotGitRepository()
639
elif response.code != 200:
640
raise GitProtocolError("unexpected http resp %d for %s" %
641
(response.code, url))
643
# TODO: Optimization available by adding `preload_content=False` to the
644
# request and just passing the `read` method on instead of going via
645
# `BytesIO`, if we can guarantee that the entire response is consumed
646
# before issuing the next to still allow for connection reuse from the
648
if response.getheader("Content-Encoding") == "gzip":
649
read = gzip.GzipFile(fileobj=response).read
653
class WrapResponse(object):
655
def __init__(self, response):
656
self._response = response
657
self.status = response.code
658
self.content_type = response.getheader("Content-Type")
659
self.redirect_location = response.geturl()
662
self._response.close()
664
return WrapResponse(response), read
667
class RemoteGitControlDirFormat(GitControlDirFormat):
668
"""The .git directory control format."""
670
supports_workingtrees = False
673
def _known_formats(self):
674
return set([RemoteGitControlDirFormat()])
676
def get_branch_format(self):
677
return RemoteGitBranchFormat()
679
def is_initializable(self):
682
def is_supported(self):
685
def open(self, transport, _found=None):
686
"""Open this directory.
689
# we dont grok readonly - git isn't integrated with transport.
691
if url.startswith('readonly+'):
692
url = url[len('readonly+'):]
693
scheme = urlparse.urlsplit(transport.external_url())[0]
694
if isinstance(transport, GitSmartTransport):
695
client = transport._get_client()
696
client_path = transport._get_path()
697
elif scheme in ("http", "https"):
698
client = BzrGitHttpClient(transport)
699
client_path, _ = urlutils.split_segment_parameters(transport._path)
700
elif scheme == 'file':
701
client = dulwich.client.LocalGitClient()
702
client_path = transport.local_abspath('.')
704
raise NotBranchError(transport.base)
706
pass # TODO(jelmer): Actually probe for something
707
return RemoteGitDir(transport, self, client, client_path)
709
def get_format_description(self):
710
return "Remote Git Repository"
712
def initialize_on_transport(self, transport):
713
raise UninitializableFormat(self)
715
def supports_transport(self, transport):
717
external_url = transport.external_url()
718
except InProcessTransport:
719
raise NotBranchError(path=transport.base)
720
return (external_url.startswith("http:") or
721
external_url.startswith("https:") or
722
external_url.startswith("git+") or
723
external_url.startswith("git:"))
726
class GitRemoteRevisionTree(RevisionTree):
728
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
729
"""Create an archive of this tree.
731
:param format: Format name (e.g. 'tar')
732
:param name: target file name
733
:param root: Root directory name (or None)
734
:param subdir: Subdirectory to export (or None)
735
:return: Iterator over archive chunks
737
commit = self._repository.lookup_bzr_revision_id(
738
self.get_revision_id())[0]
739
f = tempfile.SpooledTemporaryFile()
740
# git-upload-archive(1) generaly only supports refs. So let's see if we
744
self._repository.controldir.get_refs_container().as_dict().items()}
746
committish = reverse_refs[commit]
748
# No? Maybe the user has uploadArchive.allowUnreachable enabled.
749
# Let's hope for the best.
751
self._repository.archive(
752
format, committish, f.write,
753
subdirs=([subdir] if subdir else None),
754
prefix=(root+'/') if root else '')
756
return osutils.file_iterator(f)
102
759
class RemoteGitRepository(GitRepository):
104
def __init__(self, gitdir, lockfiles):
105
GitRepository.__init__(self, gitdir, lockfiles)
107
def fetch_pack(self, determine_wants, graph_walker, pack_data,
763
return self.control_url
765
def get_parent_map(self, revids):
766
raise GitSmartRemoteNotSupported(self.get_parent_map, self)
768
def archive(self, *args, **kwargs):
769
return self.controldir.archive(*args, **kwargs)
771
def fetch_pack(self, determine_wants, graph_walker, pack_data,
109
self._transport.fetch_pack(determine_wants, graph_walker, pack_data,
773
return self.controldir.fetch_pack(determine_wants, graph_walker,
776
def send_pack(self, get_changed_refs, generate_pack_data):
777
return self.controldir.send_pack(get_changed_refs, generate_pack_data)
779
def fetch_objects(self, determine_wants, graph_walker, resolve_ext_ref,
781
fd, path = tempfile.mkstemp(suffix=".pack")
783
self.fetch_pack(determine_wants, graph_walker,
784
lambda x: os.write(fd, x), progress)
787
if os.path.getsize(path) == 0:
788
return EmptyObjectStoreIterator()
789
return TemporaryPackIterator(path[:-len(".pack")], resolve_ext_ref)
791
def lookup_bzr_revision_id(self, bzr_revid, mapping=None):
792
# This won't work for any round-tripped bzr revisions, but it's a start..
794
return mapping_registry.revision_id_bzr_to_foreign(bzr_revid)
795
except InvalidRevisionId:
796
raise NoSuchRevision(self, bzr_revid)
798
def lookup_foreign_revision_id(self, foreign_revid, mapping=None):
799
"""Lookup a revision id.
803
mapping = self.get_mapping()
804
# Not really an easy way to parse foreign revids here..
805
return mapping.revision_id_foreign_to_bzr(foreign_revid)
807
def revision_tree(self, revid):
808
return GitRemoteRevisionTree(self, revid)
810
def get_revisions(self, revids):
811
raise GitSmartRemoteNotSupported(self.get_revisions, self)
813
def has_revisions(self, revids):
814
raise GitSmartRemoteNotSupported(self.get_revisions, self)
817
class RemoteGitTagDict(GitTags):
819
def set_tag(self, name, revid):
820
sha = self.branch.lookup_bzr_revision_id(revid)[0]
821
self._set_ref(name, sha)
823
def delete_tag(self, name):
824
self._set_ref(name, dulwich.client.ZERO_SHA)
826
def _set_ref(self, name, sha):
827
ref = tag_name_to_ref(name)
828
def get_changed_refs(old_refs):
830
if sha == dulwich.client.ZERO_SHA and ref not in ret:
831
raise NoSuchTag(name)
834
def generate_pack_data(have, want, ofs_delta=False):
835
return pack_objects_to_data([])
836
self.repository.send_pack(get_changed_refs, generate_pack_data)
113
839
class RemoteGitBranch(GitBranch):
115
def __init__(self, bzrdir, repository, name, lockfiles):
116
def determine_wants(heads):
117
self._ref = heads[name]
118
bzrdir.root_transport.fetch_pack(determine_wants, None, lambda x: None,
119
lambda x: mutter("git: %s" % x))
120
super(RemoteGitBranch, self).__init__(bzrdir, repository, name, self._ref, lockfiles)
841
def __init__(self, controldir, repository, name):
843
super(RemoteGitBranch, self).__init__(controldir, repository, name,
844
RemoteGitBranchFormat())
846
def last_revision_info(self):
847
raise GitSmartRemoteNotSupported(self.last_revision_info, self)
851
return self.control_url
854
def control_url(self):
857
def revision_id_to_revno(self, revision_id):
858
raise GitSmartRemoteNotSupported(self.revision_id_to_revno, self)
122
860
def last_revision(self):
123
return self.mapping.revision_id_foreign_to_bzr(self._ref)
861
return self.lookup_foreign_revision_id(self.head)
865
if self._sha is not None:
867
refs = self.controldir.get_refs_container()
868
name = branch_name_to_ref(self.name)
870
self._sha = refs[name]
872
raise NoSuchRef(name, self.repository.user_url, refs)
875
def _synchronize_history(self, destination, revision_id):
876
"""See Branch._synchronize_history()."""
877
destination.generate_revision_history(self.last_revision())
879
def _get_parent_location(self):
882
def get_push_location(self):
885
def set_push_location(self, url):
888
def _iter_tag_refs(self):
889
"""Iterate over the tag refs.
891
:param refs: Refs dictionary (name -> git sha1)
892
:return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
894
refs = self.controldir.get_refs_container()
895
for ref_name, unpeeled in refs.as_dict().items():
897
tag_name = ref_to_tag_name(ref_name)
898
except (ValueError, UnicodeDecodeError):
900
peeled = refs.get_peeled(ref_name)
903
peeled = refs.peel_sha(unpeeled).id
905
# Let's just hope it's a commit
907
if not isinstance(tag_name, text_type):
908
raise TypeError(tag_name)
909
yield (ref_name, tag_name, peeled, unpeeled)
912
def remote_refs_dict_to_container(refs_dict, symrefs_dict={}):
915
for k, v in refs_dict.items():
921
for name, target in symrefs_dict.items():
922
base[name] = SYMREF + target
923
ret = DictRefsContainer(base)