Merge lp:~jelmer/brz/bundle-git into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/bundle-git
Merge into: lp:brz
Diff against target: 19148 lines (+18761/-4)
69 files modified
.mailmap (+5/-0)
.travis.yml (+4/-1)
breezy/plugins/git/.testr.conf (+4/-0)
breezy/plugins/git/Makefile (+80/-0)
breezy/plugins/git/TODO (+2/-0)
breezy/plugins/git/__init__.py (+440/-0)
breezy/plugins/git/annotate.py (+151/-0)
breezy/plugins/git/branch.py (+1291/-0)
breezy/plugins/git/bzr-receive-pack (+15/-0)
breezy/plugins/git/bzr-upload-pack (+15/-0)
breezy/plugins/git/cache.py (+1008/-0)
breezy/plugins/git/commands.py (+340/-0)
breezy/plugins/git/commit.py (+241/-0)
breezy/plugins/git/config.py (+63/-0)
breezy/plugins/git/dir.py (+693/-0)
breezy/plugins/git/directory.py (+30/-0)
breezy/plugins/git/errors.py (+80/-0)
breezy/plugins/git/fetch.py (+550/-0)
breezy/plugins/git/filegraph.py (+109/-0)
breezy/plugins/git/git-remote-bzr (+49/-0)
breezy/plugins/git/git-remote-bzr.1 (+33/-0)
breezy/plugins/git/git_remote_helper.py (+206/-0)
breezy/plugins/git/help.py (+36/-0)
breezy/plugins/git/hg.py (+81/-0)
breezy/plugins/git/interrepo.py (+724/-0)
breezy/plugins/git/mapping.py (+673/-0)
breezy/plugins/git/memorytree.py (+237/-0)
breezy/plugins/git/notes/git-serve.txt (+16/-0)
breezy/plugins/git/notes/mapping.txt (+44/-0)
breezy/plugins/git/notes/roundtripping.txt (+10/-0)
breezy/plugins/git/object_store.py (+860/-0)
breezy/plugins/git/pristine_tar.py (+109/-0)
breezy/plugins/git/push.py (+126/-0)
breezy/plugins/git/refs.py (+212/-0)
breezy/plugins/git/remote.py (+858/-0)
breezy/plugins/git/repository.py (+686/-0)
breezy/plugins/git/revspec.py (+127/-0)
breezy/plugins/git/roundtrip.py (+175/-0)
breezy/plugins/git/send.py (+183/-0)
breezy/plugins/git/server.py (+172/-0)
breezy/plugins/git/tests/__init__.py (+220/-0)
breezy/plugins/git/tests/test_blackbox.py (+299/-0)
breezy/plugins/git/tests/test_branch.py (+281/-0)
breezy/plugins/git/tests/test_builder.py (+260/-0)
breezy/plugins/git/tests/test_cache.py (+183/-0)
breezy/plugins/git/tests/test_dir.py (+95/-0)
breezy/plugins/git/tests/test_fetch.py (+477/-0)
breezy/plugins/git/tests/test_git_remote_helper.py (+139/-0)
breezy/plugins/git/tests/test_mapping.py (+410/-0)
breezy/plugins/git/tests/test_memorytree.py (+204/-0)
breezy/plugins/git/tests/test_object_store.py (+272/-0)
breezy/plugins/git/tests/test_pristine_tar.py (+106/-0)
breezy/plugins/git/tests/test_push.py (+121/-0)
breezy/plugins/git/tests/test_refs.py (+87/-0)
breezy/plugins/git/tests/test_remote.py (+448/-0)
breezy/plugins/git/tests/test_repository.py (+272/-0)
breezy/plugins/git/tests/test_revspec.py (+33/-0)
breezy/plugins/git/tests/test_roundtrip.py (+114/-0)
breezy/plugins/git/tests/test_server.py (+85/-0)
breezy/plugins/git/tests/test_transportgit.py (+63/-0)
breezy/plugins/git/tests/test_unpeel_map.py (+53/-0)
breezy/plugins/git/tests/test_urls.py (+48/-0)
breezy/plugins/git/tests/test_workingtree.py (+177/-0)
breezy/plugins/git/transportgit.py (+747/-0)
breezy/plugins/git/tree.py (+1252/-0)
breezy/plugins/git/unpeel_map.py (+96/-0)
breezy/plugins/git/urls.py (+46/-0)
breezy/plugins/git/workingtree.py (+1426/-0)
setup.py (+9/-3)
To merge this branch: bzr merge lp:~jelmer/brz/bundle-git
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Martin Packman Approve
Review via email: mp+345138@code.launchpad.net

Commit message

Bundle the git plugin with Breezy.

Description of the change

Bundle the git plugin with Breezy.

All tests now pass, and the plugin is fully functional - I've been using it on my git repositories without issues over the last couple of months.

See https://bugs.launchpad.net/brz-git/+bugs for a list of remaining bugs. Most of these are around support for shelves, shallow branches and rename tracking.

The code is well tested (not in the least thanks to the various per_X testsuites in core), but could be less ugly in some places.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Not reviewing all of bzr-git here obviously, but merging in to core is a great step forward, thank you!

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.mailmap'
2--- .mailmap 1970-01-01 00:00:00 +0000
3+++ .mailmap 2018-05-10 00:44:29 +0000
4@@ -0,0 +1,5 @@
5+Jelmer Vernooij <jelmer@jelmer.uk> <jelmer@jelmer.uk>
6+Jelmer Vernooij <jelmer@jelmer.uk> <jelmer@samba.org>
7+Jelmer Vernooij <jelmer@jelmer.uk> <jelmer@canonical.com>
8+INADA Naoki <songofacandy@gmail.com> <songofacandy@gmail.com>
9+Martin Packman <gzlist@googlemail.com> <martin.packman@canonical.com>
10
11=== modified file '.travis.yml'
12--- .travis.yml 2018-05-10 00:38:40 +0000
13+++ .travis.yml 2018-05-10 00:44:29 +0000
14@@ -4,6 +4,9 @@
15 update: true
16 sudo: false
17 cache: pip
18+git:
19+ depth: false
20+
21 python:
22 - 3.5
23 - 3.6
24@@ -26,7 +29,7 @@
25 install:
26 - sudo apt install python-all-dev python3-all-dev subunit
27 - travis_retry pip install -U setuptools
28- - travis_retry pip install -U pip coverage codecov flake8 testtools paramiko fastimport configobj cython testscenarios six docutils python-subunit $TEST_REQUIRE
29+ - travis_retry pip install -U pip coverage codecov flake8 testtools paramiko fastimport configobj cython testscenarios six docutils python-subunit dulwich $TEST_REQUIRE
30
31 after_success:
32 - codecov
33
34=== added directory 'breezy/plugins/git'
35=== added file 'breezy/plugins/git/.testr.conf'
36--- breezy/plugins/git/.testr.conf 1970-01-01 00:00:00 +0000
37+++ breezy/plugins/git/.testr.conf 2018-05-10 00:44:29 +0000
38@@ -0,0 +1,4 @@
39+[DEFAULT]
40+test_command=BRZ_PLUGINS_AT=git@`pwd` BRZ_PLUGIN_PATH=-site:-user ${BRZ:-../../../brz} selftest ^breezy.plugins.git. Git --subunit2 $IDOPTION $LISTOPT
41+test_id_option=--load-list $IDFILE
42+test_list_option=--list
43
44=== added file 'breezy/plugins/git/Makefile'
45--- breezy/plugins/git/Makefile 1970-01-01 00:00:00 +0000
46+++ breezy/plugins/git/Makefile 2018-05-10 00:44:29 +0000
47@@ -0,0 +1,80 @@
48+DEBUGGER ?=
49+BRZ_OPTIONS ?=
50+BRZ ?= $(shell which brz)
51+PYTHON ?= $(shell which python)
52+SETUP ?= ./setup.py
53+PYDOCTOR ?= pydoctor
54+CTAGS ?= ctags
55+PYLINT ?= pylint
56+RST2HTML ?= rst2html
57+TESTS ?= ^breezy.plugins.git. Git breezy.tests.test_info.TestInfo.test_describe_tree_format breezy.tests.test_errors.TestErrors.test_no_arg_named_message breezy.tests.test_info.TestInfo.test_describe_checkout_format
58+SUBUNIT_FILTER ?= subunit-filter --fixup-expected-failures=xfail --success --xfail
59+SUBUNIT_FORMATTER = subunit2pyunit
60+
61+all:: build
62+
63+build::
64+ $(SETUP) build
65+
66+build-inplace::
67+
68+install::
69+ $(SETUP) install
70+
71+clean::
72+ $(SETUP) clean
73+ rm -f *.so
74+
75+check:: build-inplace
76+ BRZ_PLUGINS_AT=git@$(shell pwd) BRZ_PLUGIN_PATH=-site:-user $(DEBUGGER) $(PYTHON) $(PYTHON_OPTIONS) $(BRZ) $(BRZ_OPTIONS) selftest --subunit2 $(TEST_OPTIONS) $(TESTS) | $(SUBUNIT_FILTER) | $(SUBUNIT_FORMATTER)
77+
78+list-failing-tests:
79+ $(MAKE) check SUBUNIT_FILTER="subunit-filter -F" SUBUNIT_FORMATTER=subunit-ls | grep -e "^breezy\\." | sort
80+
81+xfail:
82+ $(MAKE) -s list-failing-tests > xfail
83+
84+check-all::
85+ $(MAKE) check TESTS="^breezy.plugins.git. Git" SUBUNIT_FILTER=cat
86+
87+check-verbose::
88+ $(MAKE) check TEST_OPTIONS=-v
89+
90+check-one::
91+ $(MAKE) check TEST_OPTIONS=--one
92+
93+check-random::
94+ $(MAKE) check TEST_OPTIONS="--random=now --verbose --one"
95+
96+show-plugins::
97+ BRZ_PLUGINS_AT=git@$(shell pwd) $(BRZ) plugins -v
98+
99+lint::
100+ $(PYLINT) -f parseable *.py */*.py
101+
102+tags::
103+ $(CTAGS) -R .
104+
105+ctags:: tags
106+
107+coverage::
108+ $(MAKE) check BRZ_OPTIONS="--coverage coverage"
109+
110+.PHONY: update-pot po/brz-git.pot
111+update-pot: po/brz-git.pot
112+
113+TRANSLATABLE_PYFILES:=$(shell find . -name '*.py' \
114+ | grep -v 'tests/' \
115+ )
116+
117+po/brz-git.pot: $(PYFILES) $(DOCFILES)
118+ BRZ_PLUGINS_AT=git@$(shell pwd) brz export-pot \
119+ --plugin=git > po/brz-git.pot
120+ echo $(TRANSLATABLE_PYFILES) | xargs \
121+ xgettext --package-name "brz-git" \
122+ --msgid-bugs-address "<bazaar@lists.canonical.com>" \
123+ --copyright-holder "Canonical Ltd <canonical-bazaar@lists.canonical.com>" \
124+ --from-code ISO-8859-1 --sort-by-file --join --add-comments=i18n: \
125+ -d brz-git -p po -o brz-git.pot
126+
127+.PHONY: xfail
128
129=== added file 'breezy/plugins/git/TODO'
130--- breezy/plugins/git/TODO 1970-01-01 00:00:00 +0000
131+++ breezy/plugins/git/TODO 2018-05-10 00:44:29 +0000
132@@ -0,0 +1,2 @@
133++ Rename detection - pad.lv/1760740
134++ Shallow branches - pad.lv/1764215
135
136=== added file 'breezy/plugins/git/__init__.py'
137--- breezy/plugins/git/__init__.py 1970-01-01 00:00:00 +0000
138+++ breezy/plugins/git/__init__.py 2018-05-10 00:44:29 +0000
139@@ -0,0 +1,440 @@
140+# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
141+# Copyright (C) 2006-2009 Canonical Ltd
142+
143+# Authors: Robert Collins <robert.collins@canonical.com>
144+# Jelmer Vernooij <jelmer@jelmer.uk>
145+# John Carr <john.carr@unrouted.co.uk>
146+#
147+# This program is free software; you can redistribute it and/or modify
148+# it under the terms of the GNU General Public License as published by
149+# the Free Software Foundation; either version 2 of the License, or
150+# (at your option) any later version.
151+#
152+# This program is distributed in the hope that it will be useful,
153+# but WITHOUT ANY WARRANTY; without even the implied warranty of
154+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
155+# GNU General Public License for more details.
156+#
157+# You should have received a copy of the GNU General Public License
158+# along with this program; if not, write to the Free Software
159+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
160+
161+
162+"""A GIT branch and repository format implementation for bzr."""
163+
164+from __future__ import absolute_import
165+
166+import os
167+import sys
168+
169+dulwich_minimum_version = (0, 19, 0)
170+
171+from breezy.i18n import gettext
172+
173+from ... import (
174+ __version__ as breezy_version,
175+ errors as bzr_errors,
176+ trace,
177+ version_info,
178+ )
179+
180+from ...controldir import (
181+ ControlDirFormat,
182+ Prober,
183+ format_registry,
184+ network_format_registry as controldir_network_format_registry,
185+ )
186+
187+from ...transport import (
188+ register_lazy_transport,
189+ register_transport_proto,
190+ transport_server_registry,
191+ )
192+from ...commands import (
193+ plugin_cmds,
194+ )
195+
196+
197+if getattr(sys, "frozen", None):
198+ # allow import additional libs from ./_lib for bzr.exe only
199+ sys.path.append(os.path.normpath(
200+ os.path.join(os.path.dirname(__file__), '_lib')))
201+
202+
203+def import_dulwich():
204+ try:
205+ from dulwich import __version__ as dulwich_version
206+ except ImportError:
207+ raise bzr_errors.DependencyNotPresent("dulwich",
208+ "bzr-git: Please install dulwich, https://www.dulwich.io/")
209+ else:
210+ if dulwich_version < dulwich_minimum_version:
211+ raise bzr_errors.DependencyNotPresent("dulwich",
212+ "bzr-git: Dulwich is too old; at least %d.%d.%d is required" %
213+ dulwich_minimum_version)
214+
215+
216+_versions_checked = False
217+def lazy_check_versions():
218+ global _versions_checked
219+ if _versions_checked:
220+ return
221+ import_dulwich()
222+ _versions_checked = True
223+
224+format_registry.register_lazy('git',
225+ __name__ + ".dir", "LocalGitControlDirFormat",
226+ help='GIT repository.', native=False, experimental=False,
227+ )
228+
229+format_registry.register_lazy('git-bare',
230+ __name__ + ".dir", "BareLocalGitControlDirFormat",
231+ help='Bare GIT repository (no working tree).', native=False,
232+ experimental=False,
233+ )
234+
235+from ...revisionspec import (RevisionSpec_dwim, revspec_registry)
236+revspec_registry.register_lazy("git:", __name__ + ".revspec",
237+ "RevisionSpec_git")
238+RevisionSpec_dwim.append_possible_lazy_revspec(
239+ __name__ + ".revspec", "RevisionSpec_git")
240+
241+
242+class LocalGitProber(Prober):
243+
244+ def probe_transport(self, transport):
245+ try:
246+ external_url = transport.external_url()
247+ except bzr_errors.InProcessTransport:
248+ raise bzr_errors.NotBranchError(path=transport.base)
249+ if (external_url.startswith("http:") or
250+ external_url.startswith("https:")):
251+ # Already handled by RemoteGitProber
252+ raise bzr_errors.NotBranchError(path=transport.base)
253+ from ... import urlutils
254+ if urlutils.split(transport.base)[1] == ".git":
255+ raise bzr_errors.NotBranchError(path=transport.base)
256+ if not transport.has_any(['objects', '.git/objects', '.git']):
257+ raise bzr_errors.NotBranchError(path=transport.base)
258+ lazy_check_versions()
259+ from .dir import (
260+ BareLocalGitControlDirFormat,
261+ LocalGitControlDirFormat,
262+ )
263+ if transport.has_any(['.git/objects', '.git']):
264+ return LocalGitControlDirFormat()
265+ if transport.has('info') and transport.has('objects'):
266+ return BareLocalGitControlDirFormat()
267+ raise bzr_errors.NotBranchError(path=transport.base)
268+
269+ @classmethod
270+ def known_formats(cls):
271+ from .dir import (
272+ BareLocalGitControlDirFormat,
273+ LocalGitControlDirFormat,
274+ )
275+ return set([BareLocalGitControlDirFormat(), LocalGitControlDirFormat()])
276+
277+
278+def user_agent_for_github():
279+ # GitHub requires we lie. https://github.com/dulwich/dulwich/issues/562
280+ return "git/Breezy/%s" % breezy_version
281+
282+
283+class RemoteGitProber(Prober):
284+
285+ def probe_http_transport(self, transport):
286+ from ... import urlutils
287+ base_url, _ = urlutils.split_segment_parameters(transport.external_url())
288+ url = urlutils.join(base_url, "info/refs") + "?service=git-upload-pack"
289+ from ...transport.http import Request
290+ headers = {"Content-Type": "application/x-git-upload-pack-request"}
291+ req = Request('GET', url, accepted_errors=[200, 403, 404, 405],
292+ headers=headers)
293+ if req.get_host() == "github.com":
294+ # GitHub requires we lie. https://github.com/dulwich/dulwich/issues/562
295+ req.add_header("User-Agent", user_agent_for_github())
296+ elif req.get_host() == "bazaar.launchpad.net":
297+ # Don't attempt Git probes against bazaar.launchpad.net; pad.lv/1744830
298+ raise bzr_errors.NotBranchError(transport.base)
299+ req.follow_redirections = True
300+ resp = transport._perform(req)
301+ if resp.code in (404, 405):
302+ raise bzr_errors.NotBranchError(transport.base)
303+ headers = resp.headers
304+ ct = headers.getheader("Content-Type")
305+ if ct is None:
306+ raise bzr_errors.NotBranchError(transport.base)
307+ if ct.startswith("application/x-git"):
308+ from .remote import RemoteGitControlDirFormat
309+ return RemoteGitControlDirFormat()
310+ else:
311+ from .dir import (
312+ BareLocalGitControlDirFormat,
313+ )
314+ ret = BareLocalGitControlDirFormat()
315+ ret._refs_text = resp.read()
316+ return ret
317+
318+ def probe_transport(self, transport):
319+ try:
320+ external_url = transport.external_url()
321+ except bzr_errors.InProcessTransport:
322+ raise bzr_errors.NotBranchError(path=transport.base)
323+
324+ if (external_url.startswith("http:") or
325+ external_url.startswith("https:")):
326+ return self.probe_http_transport(transport)
327+
328+ if (not external_url.startswith("git://") and
329+ not external_url.startswith("git+")):
330+ raise bzr_errors.NotBranchError(transport.base)
331+
332+ # little ugly, but works
333+ from .remote import (
334+ GitSmartTransport,
335+ RemoteGitControlDirFormat,
336+ )
337+ if isinstance(transport, GitSmartTransport):
338+ return RemoteGitControlDirFormat()
339+ raise bzr_errors.NotBranchError(path=transport.base)
340+
341+ @classmethod
342+ def known_formats(cls):
343+ from .remote import RemoteGitControlDirFormat
344+ return set([RemoteGitControlDirFormat()])
345+
346+
347+ControlDirFormat.register_prober(LocalGitProber)
348+ControlDirFormat._server_probers.append(RemoteGitProber)
349+
350+register_transport_proto('git://',
351+ help="Access using the Git smart server protocol.")
352+register_transport_proto('git+ssh://',
353+ help="Access using the Git smart server protocol over SSH.")
354+
355+register_lazy_transport("git://", __name__ + '.remote',
356+ 'TCPGitSmartTransport')
357+register_lazy_transport("git+ssh://", __name__ + '.remote',
358+ 'SSHGitSmartTransport')
359+
360+
361+plugin_cmds.register_lazy("cmd_git_import", [], __name__ + ".commands")
362+plugin_cmds.register_lazy("cmd_git_object", ["git-objects", "git-cat"],
363+ __name__ + ".commands")
364+plugin_cmds.register_lazy("cmd_git_refs", [], __name__ + ".commands")
365+plugin_cmds.register_lazy("cmd_git_apply", [], __name__ + ".commands")
366+plugin_cmds.register_lazy("cmd_git_push_pristine_tar_deltas",
367+ ['git-push-pristine-tar', 'git-push-pristine'],
368+ __name__ + ".commands")
369+
370+def extract_git_foreign_revid(rev):
371+ try:
372+ foreign_revid = rev.foreign_revid
373+ except AttributeError:
374+ from .mapping import mapping_registry
375+ foreign_revid, mapping = \
376+ mapping_registry.parse_revision_id(rev.revision_id)
377+ return foreign_revid
378+ else:
379+ from .mapping import foreign_vcs_git
380+ if rev.mapping.vcs == foreign_vcs_git:
381+ return foreign_revid
382+ else:
383+ raise bzr_errors.InvalidRevisionId(rev.revision_id, None)
384+
385+
386+def update_stanza(rev, stanza):
387+ mapping = getattr(rev, "mapping", None)
388+ try:
389+ git_commit = extract_git_foreign_revid(rev)
390+ except bzr_errors.InvalidRevisionId:
391+ pass
392+ else:
393+ stanza.add("git-commit", git_commit)
394+
395+from ...hooks import install_lazy_named_hook
396+install_lazy_named_hook("breezy.version_info_formats.format_rio",
397+ "RioVersionInfoBuilder.hooks", "revision", update_stanza,
398+ "git commits")
399+
400+
401+transport_server_registry.register_lazy('git',
402+ __name__ + '.server',
403+ 'serve_git',
404+ 'Git Smart server protocol over TCP. (default port: 9418)')
405+
406+transport_server_registry.register_lazy('git-receive-pack',
407+ __name__ + '.server',
408+ 'serve_git_receive_pack',
409+ help='Git Smart server receive pack command. (inetd mode only)')
410+transport_server_registry.register_lazy('git-upload-pack',
411+ __name__ + 'git.server',
412+ 'serve_git_upload_pack',
413+ help='Git Smart server upload pack command. (inetd mode only)')
414+
415+from ...repository import (
416+ format_registry as repository_format_registry,
417+ network_format_registry as repository_network_format_registry,
418+ )
419+repository_network_format_registry.register_lazy('git',
420+ __name__ + '.repository', 'GitRepositoryFormat')
421+
422+register_extra_lazy_repository_format = getattr(repository_format_registry,
423+ "register_extra_lazy")
424+register_extra_lazy_repository_format(__name__ + '.repository',
425+ 'GitRepositoryFormat')
426+
427+from ...branch import (
428+ network_format_registry as branch_network_format_registry,
429+ )
430+branch_network_format_registry.register_lazy('git',
431+ __name__ + '.branch', 'LocalGitBranchFormat')
432+
433+
434+from ...branch import (
435+ format_registry as branch_format_registry,
436+ )
437+branch_format_registry.register_extra_lazy(
438+ __name__ + '.branch',
439+ 'LocalGitBranchFormat',
440+ )
441+branch_format_registry.register_extra_lazy(
442+ __name__ + '.remote',
443+ 'RemoteGitBranchFormat',
444+ )
445+
446+
447+from ...workingtree import (
448+ format_registry as workingtree_format_registry,
449+ )
450+workingtree_format_registry.register_extra_lazy(
451+ __name__ + '.workingtree',
452+ 'GitWorkingTreeFormat',
453+ )
454+
455+controldir_network_format_registry.register_lazy('git',
456+ __name__ + ".dir", "GitControlDirFormat")
457+
458+
459+try:
460+ from ...registry import register_lazy
461+except ImportError:
462+ from ...diff import format_registry as diff_format_registry
463+ diff_format_registry.register_lazy('git', __name__ + '.send',
464+ 'GitDiffTree', 'Git am-style diff format')
465+
466+ from ...send import (
467+ format_registry as send_format_registry,
468+ )
469+ send_format_registry.register_lazy('git', __name__ + '.send',
470+ 'send_git', 'Git am-style diff format')
471+
472+ from ...directory_service import directories
473+ directories.register_lazy('github:', __name__ + '.directory',
474+ 'GitHubDirectory',
475+ 'GitHub directory.')
476+ directories.register_lazy('git@github.com:', __name__ + '.directory',
477+ 'GitHubDirectory',
478+ 'GitHub directory.')
479+
480+ from ...help_topics import (
481+ topic_registry,
482+ )
483+ topic_registry.register_lazy('git', __name__ + '.help', 'help_git',
484+ 'Using Bazaar with Git')
485+
486+ from ...foreign import (
487+ foreign_vcs_registry,
488+ )
489+ foreign_vcs_registry.register_lazy("git",
490+ __name__ + ".mapping", "foreign_vcs_git", "Stupid content tracker")
491+else:
492+ register_lazy("breezy.diff", "format_registry",
493+ 'git', __name__ + '.send', 'GitDiffTree',
494+ 'Git am-style diff format')
495+ register_lazy("breezy.send", "format_registry",
496+ 'git', __name__ + '.send', 'send_git',
497+ 'Git am-style diff format')
498+ register_lazy('breezy.directory_service', 'directories', 'github:',
499+ __name__ + '.directory', 'GitHubDirectory',
500+ 'GitHub directory.')
501+ register_lazy('breezy.directory_service', 'directories',
502+ 'git@github.com:', __name__ + '.directory',
503+ 'GitHubDirectory', 'GitHub directory.')
504+ register_lazy('breezy.help_topics', 'topic_registry',
505+ 'git', __name__ + '.help', 'help_git',
506+ 'Using Bazaar with Git')
507+ register_lazy('breezy.foreign', 'foreign_vcs_registry', "git",
508+ __name__ + ".mapping", "foreign_vcs_git", "Stupid content tracker")
509+
510+def update_git_cache(repository, revid):
511+ """Update the git cache after a local commit."""
512+ if getattr(repository, "_git", None) is not None:
513+ return # No need to update cache for git repositories
514+
515+ if not repository.control_transport.has("git"):
516+ return # No existing cache, don't bother updating
517+ try:
518+ lazy_check_versions()
519+ except bzr_errors.DependencyNotPresent, e:
520+ # dulwich is probably missing. silently ignore
521+ trace.mutter("not updating git map for %r: %s",
522+ repository, e)
523+
524+ from .object_store import BazaarObjectStore
525+ store = BazaarObjectStore(repository)
526+ with store.lock_write():
527+ try:
528+ parent_revisions = set(repository.get_parent_map([revid])[revid])
529+ except KeyError:
530+ # Isn't this a bit odd - how can a revision that was just committed be missing?
531+ return
532+ missing_revisions = store._missing_revisions(parent_revisions)
533+ if not missing_revisions:
534+ # Only update if the cache was up to date previously
535+ store._update_sha_map_revision(revid)
536+
537+
538+def post_commit_update_cache(local_branch, master_branch, old_revno, old_revid,
539+ new_revno, new_revid):
540+ if local_branch is not None:
541+ update_git_cache(local_branch.repository, new_revid)
542+ update_git_cache(master_branch.repository, new_revid)
543+
544+
545+def loggerhead_git_hook(branch_app, environ):
546+ branch = branch_app.branch
547+ config_stack = branch.get_config_stack()
548+ if config_stack.get('http_git'):
549+ return None
550+ from .server import git_http_hook
551+ return git_http_hook(branch, environ['REQUEST_METHOD'],
552+ environ['PATH_INFO'])
553+
554+install_lazy_named_hook("breezy.branch",
555+ "Branch.hooks", "post_commit", post_commit_update_cache,
556+ "git cache")
557+install_lazy_named_hook("breezy.plugins.loggerhead.apps.branch",
558+ "BranchWSGIApp.hooks", "controller",
559+ loggerhead_git_hook, "git support")
560+
561+
562+from ...config import (
563+ option_registry,
564+ Option,
565+ bool_from_store,
566+ )
567+
568+option_registry.register(
569+ Option('git.http',
570+ default=None, from_unicode=bool_from_store, invalid='warning',
571+ help='''\
572+Allow fetching of Git packs over HTTP.
573+
574+This enables support for fetching Git packs over HTTP in Loggerhead.
575+'''))
576+
577+def test_suite():
578+ from . import tests
579+ return tests.test_suite()
580
581=== added file 'breezy/plugins/git/annotate.py'
582--- breezy/plugins/git/annotate.py 1970-01-01 00:00:00 +0000
583+++ breezy/plugins/git/annotate.py 2018-05-10 00:44:29 +0000
584@@ -0,0 +1,151 @@
585+# Copyright (C) 2018 Jelmer Vernooij <jelmer@jelmer.uk>
586+#
587+# This program is free software; you can redistribute it and/or modify
588+# it under the terms of the GNU General Public License as published by
589+# the Free Software Foundation; either version 2 of the License, or
590+# (at your option) any later version.
591+#
592+# This program is distributed in the hope that it will be useful,
593+# but WITHOUT ANY WARRANTY; without even the implied warranty of
594+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
595+# GNU General Public License for more details.
596+#
597+# You should have received a copy of the GNU General Public License
598+# along with this program; if not, write to the Free Software
599+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
600+
601+"""Annotate."""
602+
603+from __future__ import absolute_import
604+
605+from dulwich.errors import (
606+ NotTreeError,
607+ )
608+from dulwich.object_store import (
609+ tree_lookup_path,
610+ )
611+
612+from ...errors import UnavailableRepresentation
613+from ...graph import Graph
614+from ...revision import (
615+ NULL_REVISION,
616+ )
617+
618+from .filegraph import GitFileLastChangeScanner
619+
620+
621+class GitFulltextContentFactory(object):
622+ """Static data content factory.
623+
624+ This takes a fulltext when created and just returns that during
625+ get_bytes_as('fulltext').
626+
627+ :ivar sha1: None, or the sha1 of the content fulltext.
628+ :ivar storage_kind: The native storage kind of this factory. Always
629+ 'fulltext'.
630+ :ivar key: The key of this content. Each key is a tuple with a single
631+ string in it.
632+ :ivar parents: A tuple of parent keys for self.key. If the object has
633+ no parent information, None (as opposed to () for an empty list of
634+ parents).
635+ """
636+
637+ def __init__(self, store, path, revision, blob_id):
638+ """Create a ContentFactory."""
639+ self.store = store
640+ self.key = (path, revision)
641+ self.storage_kind = 'fulltext'
642+ self.parents = None
643+ self.blob_id = blob_id
644+
645+ def get_bytes_as(self, storage_kind):
646+ if storage_kind == 'fulltext':
647+ return self.store[self.blob_id].as_raw_string()
648+ elif storage_kind == 'chunked':
649+ return self.store[self.blob_id].as_raw_chunks()
650+ raise UnavailableRepresentation(self.key, storage_kind,
651+ 'fulltext')
652+
653+
654+class GitAbsentContentFactory(object):
655+ """Absent data content factory.
656+
657+ :ivar sha1: None, or the sha1 of the content fulltext.
658+ :ivar storage_kind: The native storage kind of this factory. Always
659+ 'fulltext'.
660+ :ivar key: The key of this content. Each key is a tuple with a single
661+ string in it.
662+ :ivar parents: A tuple of parent keys for self.key. If the object has
663+ no parent information, None (as opposed to () for an empty list of
664+ parents).
665+ """
666+
667+ def __init__(self, store, path, revision):
668+ """Create a ContentFactory."""
669+ self.store = store
670+ self.key = (path, revision)
671+ self.storage_kind = 'absent'
672+ self.parents = None
673+
674+ def get_bytes_as(self, storage_kind):
675+ raise ValueError
676+
677+
678+class AnnotateProvider(object):
679+
680+ def __init__(self, change_scanner):
681+ self.change_scanner = change_scanner
682+ self.store = self.change_scanner.repository._git.object_store
683+
684+ def _get_parents(self, path, text_revision):
685+ commit_id, mapping = self.change_scanner.repository.lookup_bzr_revision_id(
686+ text_revision)
687+ text_parents = []
688+ for commit_parent in self.store[commit_id].parents:
689+ try:
690+ (path, text_parent) = self.change_scanner.find_last_change_revision(path, commit_parent)
691+ except KeyError:
692+ continue
693+ if text_parent not in text_parents:
694+ text_parents.append(text_parent)
695+ return tuple([(path,
696+ self.change_scanner.repository.lookup_foreign_revision_id(p)) for p
697+ in text_parents])
698+
699+ def get_parent_map(self, keys):
700+ ret = {}
701+ for key in keys:
702+ (path, text_revision) = key
703+ if text_revision == NULL_REVISION:
704+ ret[key] = ()
705+ continue
706+ try:
707+ ret[key] = self._get_parents(path, text_revision)
708+ except KeyError:
709+ pass
710+ return ret
711+
712+ def get_record_stream(self, keys, ordering, include_delta_closure):
713+ if ordering == 'topological':
714+ graph = Graph(self)
715+ keys = graph.iter_topo_order(keys)
716+ store = self.change_scanner.repository._git.object_store
717+ for (path, text_revision) in keys:
718+ try:
719+ commit_id, mapping = self.change_scanner.repository.lookup_bzr_revision_id(
720+ text_revision)
721+ except errors.NoSuchRevision:
722+ yield GitAbsentContentFactory(store, path, text_revision)
723+ continue
724+
725+ try:
726+ tree_id = store[commit_id].tree
727+ except KeyError:
728+ yield GitAbsentContentFactory(store, path, text_revision)
729+ continue
730+ try:
731+ (mode, blob_sha) = tree_lookup_path(store.__getitem__, tree_id, path)
732+ except KeyError:
733+ yield GitAbsentContentFactory(store, path, text_revision)
734+ else:
735+ yield GitFulltextContentFactory(store, path, text_revision, blob_sha)
736
737=== added file 'breezy/plugins/git/branch.py'
738--- breezy/plugins/git/branch.py 1970-01-01 00:00:00 +0000
739+++ breezy/plugins/git/branch.py 2018-05-10 00:44:29 +0000
740@@ -0,0 +1,1291 @@
741+# Copyright (C) 2007,2012 Canonical Ltd
742+# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
743+#
744+# This program is free software; you can redistribute it and/or modify
745+# it under the terms of the GNU General Public License as published by
746+# the Free Software Foundation; either version 2 of the License, or
747+# (at your option) any later version.
748+#
749+# This program is distributed in the hope that it will be useful,
750+# but WITHOUT ANY WARRANTY; without even the implied warranty of
751+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
752+# GNU General Public License for more details.
753+#
754+# You should have received a copy of the GNU General Public License
755+# along with this program; if not, write to the Free Software
756+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
757+
758+"""An adapter between a Git Branch and a Bazaar Branch"""
759+
760+from __future__ import absolute_import
761+
762+from cStringIO import StringIO
763+from collections import defaultdict
764+
765+from dulwich.objects import (
766+ NotCommitError,
767+ ZERO_SHA,
768+ )
769+from dulwich.repo import check_ref_format
770+
771+from ... import (
772+ branch,
773+ config,
774+ controldir,
775+ errors,
776+ lock,
777+ repository as _mod_repository,
778+ revision,
779+ tag,
780+ trace,
781+ transport,
782+ urlutils,
783+ )
784+from ...revision import (
785+ NULL_REVISION,
786+ )
787+from ...trace import (
788+ is_quiet,
789+ mutter,
790+ warning,
791+ )
792+
793+from .config import (
794+ GitBranchConfig,
795+ GitBranchStack,
796+ )
797+from .errors import (
798+ NoPushSupport,
799+ NoSuchRef,
800+ )
801+from .push import (
802+ remote_divergence,
803+ )
804+from .refs import (
805+ branch_name_to_ref,
806+ is_tag,
807+ ref_to_branch_name,
808+ ref_to_tag_name,
809+ remote_refs_dict_to_tag_refs,
810+ tag_name_to_ref,
811+ )
812+from .unpeel_map import (
813+ UnpeelMap,
814+ )
815+from .urls import git_url_to_bzr_url
816+
817+from ...foreign import ForeignBranch
818+
819+
820+class GitPullResult(branch.PullResult):
821+ """Result of a pull from a Git branch."""
822+
823+ def _lookup_revno(self, revid):
824+ if type(revid) is not str:
825+ raise TypeError(revid)
826+ # Try in source branch first, it'll be faster
827+ with self.target_branch.lock_read():
828+ return self.target_branch.revision_id_to_revno(revid)
829+
830+ @property
831+ def old_revno(self):
832+ return self._lookup_revno(self.old_revid)
833+
834+ @property
835+ def new_revno(self):
836+ return self._lookup_revno(self.new_revid)
837+
838+
839+class GitTags(tag.BasicTags):
840+ """Ref-based tag dictionary."""
841+
842+ def __init__(self, branch):
843+ self.branch = branch
844+ self.repository = branch.repository
845+
846+ def _merge_to_remote_git(self, target_repo, source_tag_refs, overwrite=False):
847+ updates = {}
848+ conflicts = []
849+ def get_changed_refs(old_refs):
850+ ret = dict(old_refs)
851+ for ref_name, tag_name, peeled, unpeeled in source_tag_refs.iteritems():
852+ if old_refs.get(ref_name) == unpeeled:
853+ pass
854+ elif overwrite or not ref_name in old_refs:
855+ ret[ref_name] = unpeeled
856+ updates[tag_name] = target_repo.lookup_foreign_revision_id(peeled)
857+ else:
858+ conflicts.append(
859+ (tag_name,
860+ self.repository.lookup_foreign_revision_id(peeled),
861+ target_repo.lookup_foreign_revision_id(old_refs[ref_name])))
862+ return ret
863+ target_repo.controldir.send_pack(get_changed_refs, lambda have, want: [])
864+ return updates, conflicts
865+
866+ def _merge_to_local_git(self, target_repo, source_tag_refs, overwrite=False):
867+ conflicts = []
868+ updates = {}
869+ for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
870+ if target_repo._git.refs.get(ref_name) == unpeeled:
871+ pass
872+ elif overwrite or not ref_name in target_repo._git.refs:
873+ target_repo._git.refs[ref_name] = unpeeled or peeled
874+ updates[tag_name] = self.repository.lookup_foreign_revision_id(peeled)
875+ else:
876+ source_revid = self.repository.lookup_foreign_revision_id(peeled)
877+ try:
878+ target_revid = target_repo.lookup_foreign_revision_id(
879+ target_repo._git.refs[ref_name])
880+ except KeyError:
881+ trace.warning('%s does not point to a valid object',
882+ ref_name)
883+ continue
884+ conflicts.append((tag_name, source_revid, target_revid))
885+ return updates, conflicts
886+
887+ def _merge_to_git(self, to_tags, source_tag_refs, overwrite=False):
888+ target_repo = to_tags.repository
889+ if self.repository.has_same_location(target_repo):
890+ return {}, []
891+ try:
892+ if getattr(target_repo, "_git", None):
893+ return self._merge_to_local_git(target_repo, source_tag_refs, overwrite)
894+ else:
895+ return self._merge_to_remote_git(target_repo, source_tag_refs, overwrite)
896+ finally:
897+ to_tags.branch._tag_refs = None
898+
899+ def _merge_to_non_git(self, to_tags, source_tag_refs, overwrite=False):
900+ unpeeled_map = defaultdict(set)
901+ conflicts = []
902+ updates = {}
903+ result = dict(to_tags.get_tag_dict())
904+ for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
905+ if unpeeled is not None:
906+ unpeeled_map[peeled].add(unpeeled)
907+ try:
908+ bzr_revid = self.branch.lookup_foreign_revision_id(peeled)
909+ except NotCommitError:
910+ continue
911+ if result.get(tag_name) == bzr_revid:
912+ pass
913+ elif tag_name not in result or overwrite:
914+ result[tag_name] = bzr_revid
915+ updates[tag_name] = bzr_revid
916+ else:
917+ conflicts.append((tag_name, bzr_revid, result[n]))
918+ to_tags._set_tag_dict(result)
919+ if len(unpeeled_map) > 0:
920+ map_file = UnpeelMap.from_repository(to_tags.branch.repository)
921+ map_file.update(unpeeled_map)
922+ map_file.save_in_repository(to_tags.branch.repository)
923+ return updates, conflicts
924+
925+ def merge_to(self, to_tags, overwrite=False, ignore_master=False,
926+ source_tag_refs=None):
927+ """See Tags.merge_to."""
928+ if source_tag_refs is None:
929+ source_tag_refs = self.branch.get_tag_refs()
930+ if self == to_tags:
931+ return {}, []
932+ if isinstance(to_tags, GitTags):
933+ return self._merge_to_git(to_tags, source_tag_refs,
934+ overwrite=overwrite)
935+ else:
936+ if ignore_master:
937+ master = None
938+ else:
939+ master = to_tags.branch.get_master_branch()
940+ if master is not None:
941+ master.lock_write()
942+ try:
943+ updates, conflicts = self._merge_to_non_git(to_tags, source_tag_refs,
944+ overwrite=overwrite)
945+ if master is not None:
946+ extra_updates, extra_conflicts = self.merge_to(
947+ master.tags, overwrite=overwrite,
948+ source_tag_refs=source_tag_refs,
949+ ignore_master=ignore_master)
950+ updates.update(extra_updates)
951+ conflicts += extra_conflicts
952+ return updates, conflicts
953+ finally:
954+ if master is not None:
955+ master.unlock()
956+
957+ def get_tag_dict(self):
958+ ret = {}
959+ for (ref_name, tag_name, peeled, unpeeled) in self.branch.get_tag_refs():
960+ try:
961+ bzr_revid = self.branch.lookup_foreign_revision_id(peeled)
962+ except NotCommitError:
963+ continue
964+ else:
965+ ret[tag_name] = bzr_revid
966+ return ret
967+
968+
969+class LocalGitTagDict(GitTags):
970+ """Dictionary with tags in a local repository."""
971+
972+ def __init__(self, branch):
973+ super(LocalGitTagDict, self).__init__(branch)
974+ self.refs = self.repository.controldir._git.refs
975+
976+ def _set_tag_dict(self, to_dict):
977+ extra = set(self.refs.allkeys())
978+ for k, revid in to_dict.iteritems():
979+ name = tag_name_to_ref(k)
980+ if name in extra:
981+ extra.remove(name)
982+ self.set_tag(k, revid)
983+ for name in extra:
984+ if is_tag(name):
985+ del self.repository._git[name]
986+
987+ def set_tag(self, name, revid):
988+ try:
989+ git_sha, mapping = self.branch.lookup_bzr_revision_id(revid)
990+ except errors.NoSuchRevision:
991+ raise errors.GhostTagsNotSupported(self)
992+ self.refs[tag_name_to_ref(name)] = git_sha
993+ self.branch._tag_refs = None
994+
995+ def delete_tag(self, name):
996+ ref = tag_name_to_ref(name)
997+ if not ref in self.refs:
998+ raise errors.NoSuchTag(name)
999+ del self.refs[ref]
1000+ self.branch._tag_refs = None
1001+
1002+
1003+class GitBranchFormat(branch.BranchFormat):
1004+
1005+ def network_name(self):
1006+ return "git"
1007+
1008+ def supports_tags(self):
1009+ return True
1010+
1011+ def supports_leaving_lock(self):
1012+ return False
1013+
1014+ def supports_tags_referencing_ghosts(self):
1015+ return False
1016+
1017+ def tags_are_versioned(self):
1018+ return False
1019+
1020+ def get_foreign_tests_branch_factory(self):
1021+ from .tests.test_branch import ForeignTestsBranchFactory
1022+ return ForeignTestsBranchFactory()
1023+
1024+ def make_tags(self, branch):
1025+ try:
1026+ return branch.tags
1027+ except AttributeError:
1028+ pass
1029+ if getattr(branch.repository, "_git", None) is None:
1030+ from .remote import RemoteGitTagDict
1031+ return RemoteGitTagDict(branch)
1032+ else:
1033+ return LocalGitTagDict(branch)
1034+
1035+ def initialize(self, a_controldir, name=None, repository=None,
1036+ append_revisions_only=None):
1037+ raise NotImplementedError(self.initialize)
1038+
1039+ def get_reference(self, controldir, name=None):
1040+ return controldir.get_branch_reference(name)
1041+
1042+ def set_reference(self, controldir, name, target):
1043+ return controldir.set_branch_reference(target, name)
1044+
1045+
1046+class LocalGitBranchFormat(GitBranchFormat):
1047+
1048+ def get_format_description(self):
1049+ return 'Local Git Branch'
1050+
1051+ @property
1052+ def _matchingcontroldir(self):
1053+ from .dir import LocalGitControlDirFormat
1054+ return LocalGitControlDirFormat()
1055+
1056+ def initialize(self, a_controldir, name=None, repository=None,
1057+ append_revisions_only=None):
1058+ from .dir import LocalGitDir
1059+ if not isinstance(a_controldir, LocalGitDir):
1060+ raise errors.IncompatibleFormat(self, a_controldir._format)
1061+ return a_controldir.create_branch(repository=repository, name=name,
1062+ append_revisions_only=append_revisions_only)
1063+
1064+
1065+class GitBranch(ForeignBranch):
1066+ """An adapter to git repositories for bzr Branch objects."""
1067+
1068+ @property
1069+ def control_transport(self):
1070+ return self._control_transport
1071+
1072+ @property
1073+ def user_transport(self):
1074+ return self._user_transport
1075+
1076+ def __init__(self, controldir, repository, ref, format):
1077+ self.repository = repository
1078+ self._format = format
1079+ self.controldir = controldir
1080+ self._lock_mode = None
1081+ self._lock_count = 0
1082+ super(GitBranch, self).__init__(repository.get_mapping())
1083+ self.ref = ref
1084+ self._head = None
1085+ self._user_transport = controldir.user_transport.clone('.')
1086+ self._control_transport = controldir.control_transport.clone('.')
1087+ self._tag_refs = None
1088+ params = {}
1089+ try:
1090+ self.name = ref_to_branch_name(ref)
1091+ except ValueError:
1092+ self.name = None
1093+ if self.ref is not None:
1094+ params = {"ref": urlutils.escape(self.ref)}
1095+ else:
1096+ if self.name != "":
1097+ params = {"branch": urlutils.escape(self.name)}
1098+ for k, v in params.items():
1099+ self._user_transport.set_segment_parameter(k, v)
1100+ self._control_transport.set_segment_parameter(k, v)
1101+ self.base = controldir.user_transport.base
1102+
1103+ def _get_checkout_format(self, lightweight=False):
1104+ """Return the most suitable metadir for a checkout of this branch.
1105+ Weaves are used if this branch's repository uses weaves.
1106+ """
1107+ if lightweight:
1108+ return controldir.format_registry.make_controldir("git")
1109+ else:
1110+ return controldir.format_registry.make_controldir("default")
1111+
1112+ def get_child_submit_format(self):
1113+ """Return the preferred format of submissions to this branch."""
1114+ ret = self.get_config_stack().get("child_submit_format")
1115+ if ret is not None:
1116+ return ret
1117+ return "git"
1118+
1119+ def get_config(self):
1120+ return GitBranchConfig(self)
1121+
1122+ def get_config_stack(self):
1123+ return GitBranchStack(self)
1124+
1125+ def _get_nick(self, local=False, possible_master_transports=None):
1126+ """Find the nick name for this branch.
1127+
1128+ :return: Branch nick
1129+ """
1130+ cs = self.repository._git.get_config_stack()
1131+ try:
1132+ return cs.get((b"branch", self.name.encode('utf-8')), b"nick").decode("utf-8")
1133+ except KeyError:
1134+ pass
1135+ return self.name or u"HEAD"
1136+
1137+ def _set_nick(self, nick):
1138+ cf = self.repository._git.get_config()
1139+ cf.set((b"branch", self.name.encode('utf-8')), b"nick", nick.encode("utf-8"))
1140+ f = StringIO()
1141+ cf.write_to_file(f)
1142+ self.repository._git._put_named_file('config', f.getvalue())
1143+
1144+ nick = property(_get_nick, _set_nick)
1145+
1146+ def __repr__(self):
1147+ return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base,
1148+ self.name)
1149+
1150+ def generate_revision_history(self, revid, last_rev=None, other_branch=None):
1151+ if last_rev is not None:
1152+ graph = self.repository.get_graph()
1153+ if not graph.is_ancestor(last_rev, revid):
1154+ # our previous tip is not merged into stop_revision
1155+ raise errors.DivergedBranches(self, other_branch)
1156+
1157+ self.set_last_revision(revid)
1158+
1159+ def lock_write(self, token=None):
1160+ if token is not None:
1161+ raise errors.TokenLockingNotSupported(self)
1162+ if self._lock_mode:
1163+ if self._lock_mode == 'r':
1164+ raise errors.ReadOnlyError(self)
1165+ self._lock_count += 1
1166+ else:
1167+ self._lock_ref()
1168+ self._lock_mode = 'w'
1169+ self._lock_count = 1
1170+ self.repository.lock_write()
1171+ return lock.LogicalLockResult(self.unlock)
1172+
1173+ def leave_lock_in_place(self):
1174+ raise NotImplementedError(self.leave_lock_in_place)
1175+
1176+ def dont_leave_lock_in_place(self):
1177+ raise NotImplementedError(self.dont_leave_lock_in_place)
1178+
1179+ def get_stacked_on_url(self):
1180+ # Git doesn't do stacking (yet...)
1181+ raise branch.UnstackableBranchFormat(self._format, self.base)
1182+
1183+ def _get_parent_location(self):
1184+ """See Branch.get_parent()."""
1185+ # FIXME: Set "origin" url from .git/config ?
1186+ cs = self.repository._git.get_config_stack()
1187+ try:
1188+ location = cs.get((b"remote", b'origin'), b"url")
1189+ except KeyError:
1190+ return None
1191+
1192+ params = {}
1193+ try:
1194+ ref = cs.get((b"remote", b"origin"), b"merge")
1195+ except KeyError:
1196+ pass
1197+ else:
1198+ if ref != 'HEAD':
1199+ try:
1200+ params['branch'] = ref_to_branch_name(ref).encode('utf-8')
1201+ except ValueError:
1202+ params['ref'] = ref.encode('utf-8')
1203+
1204+ url = git_url_to_bzr_url(location)
1205+ return urlutils.join_segment_parameters(url, params)
1206+
1207+ def set_parent(self, location):
1208+ # FIXME: Set "origin" url in .git/config ?
1209+ cs = self.repository._git.get_config()
1210+ this_url = urlutils.split_segment_parameters(self.user_url)[0]
1211+ target_url, target_params = urlutils.split_segment_parameters(location)
1212+ location = urlutils.relative_url(this_url, target_url)
1213+ cs.set((b"remote", b"origin"), b"url", location)
1214+ if 'branch' in target_params:
1215+ cs.set((b"remote", b"origin"), b"merge",
1216+ branch_name_to_ref(target_params['branch']))
1217+ elif 'ref' in target_params:
1218+ cs.set((b"remote", b"origin"), b"merge",
1219+ target_params['ref'])
1220+ else:
1221+ # TODO(jelmer): Maybe unset rather than setting to HEAD?
1222+ cs.set((b"remote", b"origin"), b"merge", 'HEAD')
1223+ f = StringIO()
1224+ cs.write_to_file(f)
1225+ self.repository._git._put_named_file('config', f.getvalue())
1226+
1227+ def break_lock(self):
1228+ raise NotImplementedError(self.break_lock)
1229+
1230+ def lock_read(self):
1231+ if self._lock_mode:
1232+ if self._lock_mode not in ('r', 'w'):
1233+ raise ValueError(self._lock_mode)
1234+ self._lock_count += 1
1235+ else:
1236+ self._lock_mode = 'r'
1237+ self._lock_count = 1
1238+ self.repository.lock_read()
1239+ return lock.LogicalLockResult(self.unlock)
1240+
1241+ def peek_lock_mode(self):
1242+ return self._lock_mode
1243+
1244+ def is_locked(self):
1245+ return (self._lock_mode is not None)
1246+
1247+ def _lock_ref(self):
1248+ pass
1249+
1250+ def _unlock_ref(self):
1251+ pass
1252+
1253+ def unlock(self):
1254+ """See Branch.unlock()."""
1255+ if self._lock_count == 0:
1256+ raise errors.LockNotHeld(self)
1257+ try:
1258+ self._lock_count -= 1
1259+ if self._lock_count == 0:
1260+ if self._lock_mode == 'w':
1261+ self._unlock_ref()
1262+ self._lock_mode = None
1263+ self._clear_cached_state()
1264+ finally:
1265+ self.repository.unlock()
1266+
1267+ def get_physical_lock_status(self):
1268+ return False
1269+
1270+ def last_revision(self):
1271+ with self.lock_read():
1272+ # perhaps should escape this ?
1273+ if self.head is None:
1274+ return revision.NULL_REVISION
1275+ return self.lookup_foreign_revision_id(self.head)
1276+
1277+ def _basic_push(self, target, overwrite=False, stop_revision=None):
1278+ return branch.InterBranch.get(self, target)._basic_push(
1279+ overwrite, stop_revision)
1280+
1281+ def lookup_foreign_revision_id(self, foreign_revid):
1282+ try:
1283+ return self.repository.lookup_foreign_revision_id(foreign_revid,
1284+ self.mapping)
1285+ except KeyError:
1286+ # Let's try..
1287+ return self.mapping.revision_id_foreign_to_bzr(foreign_revid)
1288+
1289+ def lookup_bzr_revision_id(self, revid):
1290+ return self.repository.lookup_bzr_revision_id(
1291+ revid, mapping=self.mapping)
1292+
1293+ def get_unshelver(self, tree):
1294+ raise errors.StoringUncommittedNotSupported(self)
1295+
1296+ def _clear_cached_state(self):
1297+ super(GitBranch, self)._clear_cached_state()
1298+ self._tag_refs = None
1299+
1300+ def _iter_tag_refs(self, refs):
1301+ """Iterate over the tag refs.
1302+
1303+ :param refs: Refs dictionary (name -> git sha1)
1304+ :return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1305+ """
1306+ raise NotImplementedError(self._iter_tag_refs)
1307+
1308+ def get_tag_refs(self):
1309+ with self.lock_read():
1310+ if self._tag_refs is None:
1311+ self._tag_refs = list(self._iter_tag_refs())
1312+ return self._tag_refs
1313+
1314+
1315+class LocalGitBranch(GitBranch):
1316+ """A local Git branch."""
1317+
1318+ def __init__(self, controldir, repository, ref):
1319+ super(LocalGitBranch, self).__init__(controldir, repository, ref,
1320+ LocalGitBranchFormat())
1321+
1322+ def create_checkout(self, to_location, revision_id=None, lightweight=False,
1323+ accelerator_tree=None, hardlink=False):
1324+ t = transport.get_transport(to_location)
1325+ t.ensure_base()
1326+ format = self._get_checkout_format(lightweight=lightweight)
1327+ checkout = format.initialize_on_transport(t)
1328+ if lightweight:
1329+ from_branch = checkout.set_branch_reference(target_branch=self)
1330+ else:
1331+ policy = checkout.determine_repository_policy()
1332+ repo = policy.acquire_repository()[0]
1333+
1334+ checkout_branch = checkout.create_branch()
1335+ checkout_branch.bind(self)
1336+ checkout_branch.pull(self, stop_revision=revision_id)
1337+ from_branch = None
1338+ return checkout.create_workingtree(revision_id,
1339+ from_branch=from_branch, hardlink=hardlink)
1340+
1341+ def _lock_ref(self):
1342+ self._ref_lock = self.repository._git.refs.lock_ref(self.ref)
1343+
1344+ def _unlock_ref(self):
1345+ self._ref_lock.unlock()
1346+
1347+ def break_lock(self):
1348+ self.repository._git.refs.unlock_ref(self.ref)
1349+
1350+ def fetch(self, from_branch, last_revision=None, limit=None):
1351+ return branch.InterBranch.get(from_branch, self).fetch(
1352+ stop_revision=last_revision, limit=limit)
1353+
1354+ def _gen_revision_history(self):
1355+ if self.head is None:
1356+ return []
1357+ graph = self.repository.get_graph()
1358+ ret = list(graph.iter_lefthand_ancestry(self.last_revision(),
1359+ (revision.NULL_REVISION, )))
1360+ ret.reverse()
1361+ return ret
1362+
1363+ def _get_head(self):
1364+ try:
1365+ return self.repository._git.refs[self.ref]
1366+ except KeyError:
1367+ return None
1368+
1369+ def _read_last_revision_info(self):
1370+ last_revid = self.last_revision()
1371+ graph = self.repository.get_graph()
1372+ revno = graph.find_distance_to_null(last_revid,
1373+ [(revision.NULL_REVISION, 0)])
1374+ return revno, last_revid
1375+
1376+ def set_last_revision_info(self, revno, revision_id):
1377+ self.set_last_revision(revision_id)
1378+ self._last_revision_info_cache = revno, revision_id
1379+
1380+ def set_last_revision(self, revid):
1381+ if not revid or not isinstance(revid, basestring):
1382+ raise errors.InvalidRevisionId(revision_id=revid, branch=self)
1383+ if revid == NULL_REVISION:
1384+ newhead = None
1385+ else:
1386+ (newhead, self.mapping) = self.repository.lookup_bzr_revision_id(revid)
1387+ if self.mapping is None:
1388+ raise AssertionError
1389+ self._set_head(newhead)
1390+
1391+ def _set_head(self, value):
1392+ if value == ZERO_SHA:
1393+ raise ValueError(value)
1394+ self._head = value
1395+ if value is None:
1396+ del self.repository._git.refs[self.ref]
1397+ else:
1398+ self.repository._git.refs[self.ref] = self._head
1399+ self._clear_cached_state()
1400+
1401+ head = property(_get_head, _set_head)
1402+
1403+ def get_push_location(self):
1404+ """See Branch.get_push_location."""
1405+ push_loc = self.get_config_stack().get('push_location')
1406+ return push_loc
1407+
1408+ def set_push_location(self, location):
1409+ """See Branch.set_push_location."""
1410+ self.get_config().set_user_option('push_location', location,
1411+ store=config.STORE_LOCATION)
1412+
1413+ def supports_tags(self):
1414+ return True
1415+
1416+ def store_uncommitted(self, creator):
1417+ raise errors.StoringUncommittedNotSupported(self)
1418+
1419+ def _iter_tag_refs(self):
1420+ """Iterate over the tag refs.
1421+
1422+ :param refs: Refs dictionary (name -> git sha1)
1423+ :return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
1424+ """
1425+ refs = self.repository._git.refs
1426+ for ref_name, unpeeled in refs.as_dict().iteritems():
1427+ try:
1428+ tag_name = ref_to_tag_name(ref_name)
1429+ except (ValueError, UnicodeDecodeError):
1430+ continue
1431+ peeled = refs.get_peeled(ref_name)
1432+ if peeled is None:
1433+ peeled = unpeeled
1434+ if type(tag_name) is not unicode:
1435+ raise TypeError(tag_name)
1436+ yield (ref_name, tag_name, peeled, unpeeled)
1437+
1438+ def create_memorytree(self):
1439+ from .memorytree import GitMemoryTree
1440+ return GitMemoryTree(self, self.repository._git.object_store, self.head)
1441+
1442+ def reference_parent(self, path, file_id=None, possible_transports=None):
1443+ """Return the parent branch for a tree-reference file_id
1444+
1445+ :param path: The path of the file_id in the tree
1446+ :param file_id: Optional file_id of the tree reference
1447+ :return: A branch associated with the file_id
1448+ """
1449+ # FIXME should provide multiple branches, based on config
1450+ url = urlutils.join(self.user_url, path)
1451+ return branch.Branch.open(
1452+ url,
1453+ possible_transports=possible_transports)
1454+
1455+
1456+
1457+def _quick_lookup_revno(local_branch, remote_branch, revid):
1458+ if type(revid) is not str:
1459+ raise TypeError(revid)
1460+ # Try in source branch first, it'll be faster
1461+ with local_branch.lock_read():
1462+ try:
1463+ return local_branch.revision_id_to_revno(revid)
1464+ except errors.NoSuchRevision:
1465+ graph = local_branch.repository.get_graph()
1466+ try:
1467+ return graph.find_distance_to_null(revid,
1468+ [(revision.NULL_REVISION, 0)])
1469+ except errors.GhostRevisionsHaveNoRevno:
1470+ # FIXME: Check using graph.find_distance_to_null() ?
1471+ with remote_branch.lock_read():
1472+ return remote_branch.revision_id_to_revno(revid)
1473+
1474+
1475+class GitBranchPullResult(branch.PullResult):
1476+
1477+ def __init__(self):
1478+ super(GitBranchPullResult, self).__init__()
1479+ self.new_git_head = None
1480+ self._old_revno = None
1481+ self._new_revno = None
1482+
1483+ def report(self, to_file):
1484+ if not is_quiet():
1485+ if self.old_revid == self.new_revid:
1486+ to_file.write('No revisions to pull.\n')
1487+ elif self.new_git_head is not None:
1488+ to_file.write('Now on revision %d (git sha: %s).\n' %
1489+ (self.new_revno, self.new_git_head))
1490+ else:
1491+ to_file.write('Now on revision %d.\n' % (self.new_revno,))
1492+ self._show_tag_conficts(to_file)
1493+
1494+ def _lookup_revno(self, revid):
1495+ return _quick_lookup_revno(self.target_branch, self.source_branch,
1496+ revid)
1497+
1498+ def _get_old_revno(self):
1499+ if self._old_revno is not None:
1500+ return self._old_revno
1501+ return self._lookup_revno(self.old_revid)
1502+
1503+ def _set_old_revno(self, revno):
1504+ self._old_revno = revno
1505+
1506+ old_revno = property(_get_old_revno, _set_old_revno)
1507+
1508+ def _get_new_revno(self):
1509+ if self._new_revno is not None:
1510+ return self._new_revno
1511+ return self._lookup_revno(self.new_revid)
1512+
1513+ def _set_new_revno(self, revno):
1514+ self._new_revno = revno
1515+
1516+ new_revno = property(_get_new_revno, _set_new_revno)
1517+
1518+
1519+class GitBranchPushResult(branch.BranchPushResult):
1520+
1521+ def _lookup_revno(self, revid):
1522+ return _quick_lookup_revno(self.source_branch, self.target_branch,
1523+ revid)
1524+
1525+ @property
1526+ def old_revno(self):
1527+ return self._lookup_revno(self.old_revid)
1528+
1529+ @property
1530+ def new_revno(self):
1531+ new_original_revno = getattr(self, "new_original_revno", None)
1532+ if new_original_revno:
1533+ return new_original_revno
1534+ if getattr(self, "new_original_revid", None) is not None:
1535+ return self._lookup_revno(self.new_original_revid)
1536+ return self._lookup_revno(self.new_revid)
1537+
1538+
1539+class InterFromGitBranch(branch.GenericInterBranch):
1540+ """InterBranch implementation that pulls from Git into bzr."""
1541+
1542+ @staticmethod
1543+ def _get_branch_formats_to_test():
1544+ try:
1545+ default_format = branch.format_registry.get_default()
1546+ except AttributeError:
1547+ default_format = branch.BranchFormat._default_format
1548+ from .remote import RemoteGitBranchFormat
1549+ return [
1550+ (RemoteGitBranchFormat(), default_format),
1551+ (LocalGitBranchFormat(), default_format)]
1552+
1553+ @classmethod
1554+ def _get_interrepo(self, source, target):
1555+ return _mod_repository.InterRepository.get(source.repository, target.repository)
1556+
1557+ @classmethod
1558+ def is_compatible(cls, source, target):
1559+ if not isinstance(source, GitBranch):
1560+ return False
1561+ if isinstance(target, GitBranch):
1562+ # InterLocalGitRemoteGitBranch or InterToGitBranch should be used
1563+ return False
1564+ if getattr(cls._get_interrepo(source, target), "fetch_objects", None) is None:
1565+ # fetch_objects is necessary for this to work
1566+ return False
1567+ return True
1568+
1569+ def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
1570+ self.fetch_objects(stop_revision, fetch_tags=fetch_tags, limit=limit)
1571+
1572+ def fetch_objects(self, stop_revision, fetch_tags, limit=None):
1573+ interrepo = self._get_interrepo(self.source, self.target)
1574+ if fetch_tags is None:
1575+ c = self.source.get_config_stack()
1576+ fetch_tags = c.get('branch.fetch_tags')
1577+ def determine_wants(heads):
1578+ if stop_revision is None:
1579+ try:
1580+ head = heads[self.source.ref]
1581+ except KeyError:
1582+ self._last_revid = revision.NULL_REVISION
1583+ else:
1584+ self._last_revid = self.source.lookup_foreign_revision_id(head)
1585+ else:
1586+ self._last_revid = stop_revision
1587+ real = interrepo.get_determine_wants_revids(
1588+ [self._last_revid], include_tags=fetch_tags)
1589+ return real(heads)
1590+ pack_hint, head, refs = interrepo.fetch_objects(
1591+ determine_wants, self.source.mapping, limit=limit)
1592+ if (pack_hint is not None and
1593+ self.target.repository._format.pack_compresses):
1594+ self.target.repository.pack(hint=pack_hint)
1595+ return head, refs
1596+
1597+ def _update_revisions(self, stop_revision=None, overwrite=False):
1598+ head, refs = self.fetch_objects(stop_revision, fetch_tags=None)
1599+ if overwrite:
1600+ prev_last_revid = None
1601+ else:
1602+ prev_last_revid = self.target.last_revision()
1603+ self.target.generate_revision_history(self._last_revid,
1604+ last_rev=prev_last_revid, other_branch=self.source)
1605+ return head, refs
1606+
1607+ def _basic_pull(self, stop_revision, overwrite, run_hooks,
1608+ _override_hook_target, _hook_master):
1609+ if overwrite is True:
1610+ overwrite = set(["history", "tags"])
1611+ else:
1612+ overwrite = set()
1613+ result = GitBranchPullResult()
1614+ result.source_branch = self.source
1615+ if _override_hook_target is None:
1616+ result.target_branch = self.target
1617+ else:
1618+ result.target_branch = _override_hook_target
1619+ with self.target.lock_write(), self.source.lock_read():
1620+ # We assume that during 'pull' the target repository is closer than
1621+ # the source one.
1622+ (result.old_revno, result.old_revid) = \
1623+ self.target.last_revision_info()
1624+ result.new_git_head, remote_refs = self._update_revisions(
1625+ stop_revision, overwrite=("history" in overwrite))
1626+ tags_ret = self.source.tags.merge_to(
1627+ self.target.tags, ("tags" in overwrite), ignore_master=True)
1628+ if isinstance(tags_ret, tuple):
1629+ result.tag_updates, result.tag_conflicts = tags_ret
1630+ else:
1631+ result.tag_conflicts = tags_ret
1632+ (result.new_revno, result.new_revid) = \
1633+ self.target.last_revision_info()
1634+ if _hook_master:
1635+ result.master_branch = _hook_master
1636+ result.local_branch = result.target_branch
1637+ else:
1638+ result.master_branch = result.target_branch
1639+ result.local_branch = None
1640+ if run_hooks:
1641+ for hook in branch.Branch.hooks['post_pull']:
1642+ hook(result)
1643+ return result
1644+
1645+ def pull(self, overwrite=False, stop_revision=None,
1646+ possible_transports=None, _hook_master=None, run_hooks=True,
1647+ _override_hook_target=None, local=False):
1648+ """See Branch.pull.
1649+
1650+ :param _hook_master: Private parameter - set the branch to
1651+ be supplied as the master to pull hooks.
1652+ :param run_hooks: Private parameter - if false, this branch
1653+ is being called because it's the master of the primary branch,
1654+ so it should not run its hooks.
1655+ :param _override_hook_target: Private parameter - set the branch to be
1656+ supplied as the target_branch to pull hooks.
1657+ """
1658+ # This type of branch can't be bound.
1659+ bound_location = self.target.get_bound_location()
1660+ if local and not bound_location:
1661+ raise errors.LocalRequiresBoundBranch()
1662+ master_branch = None
1663+ source_is_master = False
1664+ self.source.lock_read()
1665+ if bound_location:
1666+ # bound_location comes from a config file, some care has to be
1667+ # taken to relate it to source.user_url
1668+ normalized = urlutils.normalize_url(bound_location)
1669+ try:
1670+ relpath = self.source.user_transport.relpath(normalized)
1671+ source_is_master = (relpath == '')
1672+ except (errors.PathNotChild, urlutils.InvalidURL):
1673+ source_is_master = False
1674+ if not local and bound_location and not source_is_master:
1675+ # not pulling from master, so we need to update master.
1676+ master_branch = self.target.get_master_branch(possible_transports)
1677+ master_branch.lock_write()
1678+ try:
1679+ try:
1680+ if master_branch:
1681+ # pull from source into master.
1682+ master_branch.pull(self.source, overwrite, stop_revision,
1683+ run_hooks=False)
1684+ result = self._basic_pull(stop_revision, overwrite, run_hooks,
1685+ _override_hook_target, _hook_master=master_branch)
1686+ finally:
1687+ self.source.unlock()
1688+ finally:
1689+ if master_branch:
1690+ master_branch.unlock()
1691+ return result
1692+
1693+ def _basic_push(self, overwrite, stop_revision):
1694+ if overwrite is True:
1695+ overwrite = set(["history", "tags"])
1696+ else:
1697+ overwrite = set()
1698+ result = branch.BranchPushResult()
1699+ result.source_branch = self.source
1700+ result.target_branch = self.target
1701+ result.old_revno, result.old_revid = self.target.last_revision_info()
1702+ result.new_git_head, remote_refs = self._update_revisions(
1703+ stop_revision, overwrite=("history" in overwrite))
1704+ tags_ret = self.source.tags.merge_to(self.target.tags,
1705+ "tags" in overwrite, ignore_master=True)
1706+ (result.tag_updates, result.tag_conflicts) = tags_ret
1707+ result.new_revno, result.new_revid = self.target.last_revision_info()
1708+ return result
1709+
1710+
1711+class InterGitBranch(branch.GenericInterBranch):
1712+ """InterBranch implementation that pulls between Git branches."""
1713+
1714+ def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
1715+ raise NotImplementedError(self.fetch)
1716+
1717+
1718+class InterLocalGitRemoteGitBranch(InterGitBranch):
1719+ """InterBranch that copies from a local to a remote git branch."""
1720+
1721+ @staticmethod
1722+ def _get_branch_formats_to_test():
1723+ from .remote import RemoteGitBranchFormat
1724+ return [
1725+ (LocalGitBranchFormat(), RemoteGitBranchFormat())]
1726+
1727+ @classmethod
1728+ def is_compatible(self, source, target):
1729+ from .remote import RemoteGitBranch
1730+ return (isinstance(source, LocalGitBranch) and
1731+ isinstance(target, RemoteGitBranch))
1732+
1733+ def _basic_push(self, overwrite, stop_revision):
1734+ result = GitBranchPushResult()
1735+ result.source_branch = self.source
1736+ result.target_branch = self.target
1737+ if stop_revision is None:
1738+ stop_revision = self.source.last_revision()
1739+ def get_changed_refs(old_refs):
1740+ old_ref = old_refs.get(self.target.ref, None)
1741+ if old_ref is None:
1742+ result.old_revid = revision.NULL_REVISION
1743+ else:
1744+ result.old_revid = self.target.lookup_foreign_revision_id(old_ref)
1745+ new_ref = self.source.repository.lookup_bzr_revision_id(stop_revision)[0]
1746+ if not overwrite:
1747+ if remote_divergence(old_ref, new_ref, self.source.repository._git.object_store):
1748+ raise errors.DivergedBranches(self.source, self.target)
1749+ refs = { self.target.ref: new_ref }
1750+ result.new_revid = stop_revision
1751+ for name, sha in self.source.repository._git.refs.as_dict("refs/tags").iteritems():
1752+ refs[tag_name_to_ref(name)] = sha
1753+ return refs
1754+ self.target.repository.send_pack(get_changed_refs,
1755+ self.source.repository._git.object_store.generate_pack_data)
1756+ return result
1757+
1758+
1759+class InterGitLocalGitBranch(InterGitBranch):
1760+ """InterBranch that copies from a remote to a local git branch."""
1761+
1762+ @staticmethod
1763+ def _get_branch_formats_to_test():
1764+ from .remote import RemoteGitBranchFormat
1765+ return [
1766+ (RemoteGitBranchFormat(), LocalGitBranchFormat()),
1767+ (LocalGitBranchFormat(), LocalGitBranchFormat())]
1768+
1769+ @classmethod
1770+ def is_compatible(self, source, target):
1771+ return (isinstance(source, GitBranch) and
1772+ isinstance(target, LocalGitBranch))
1773+
1774+ def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
1775+ interrepo = _mod_repository.InterRepository.get(self.source.repository,
1776+ self.target.repository)
1777+ if stop_revision is None:
1778+ stop_revision = self.source.last_revision()
1779+ determine_wants = interrepo.get_determine_wants_revids(
1780+ [stop_revision], include_tags=fetch_tags)
1781+ interrepo.fetch_objects(determine_wants, limit=limit)
1782+
1783+ def _basic_push(self, overwrite=False, stop_revision=None):
1784+ if overwrite is True:
1785+ overwrite = set(["history", "tags"])
1786+ else:
1787+ overwrite = set()
1788+ result = GitBranchPushResult()
1789+ result.source_branch = self.source
1790+ result.target_branch = self.target
1791+ result.old_revid = self.target.last_revision()
1792+ refs, stop_revision = self.update_refs(stop_revision)
1793+ self.target.generate_revision_history(stop_revision,
1794+ (result.old_revid if ("history" not in overwrite) else None),
1795+ other_branch=self.source)
1796+ tags_ret = self.source.tags.merge_to(self.target.tags,
1797+ source_tag_refs=remote_refs_dict_to_tag_refs(refs),
1798+ overwrite=("tags" in overwrite))
1799+ if isinstance(tags_ret, tuple):
1800+ (result.tag_updates, result.tag_conflicts) = tags_ret
1801+ else:
1802+ result.tag_conflicts = tags_ret
1803+ result.new_revid = self.target.last_revision()
1804+ return result
1805+
1806+ def update_refs(self, stop_revision=None):
1807+ interrepo = _mod_repository.InterRepository.get(
1808+ self.source.repository, self.target.repository)
1809+ c = self.source.get_config_stack()
1810+ fetch_tags = c.get('branch.fetch_tags')
1811+
1812+ if stop_revision is None:
1813+ refs = interrepo.fetch(branches=["HEAD"], include_tags=fetch_tags)
1814+ try:
1815+ head = refs["HEAD"]
1816+ except KeyError:
1817+ stop_revision = revision.NULL_REVISION
1818+ else:
1819+ stop_revision = self.target.lookup_foreign_revision_id(head)
1820+ else:
1821+ refs = interrepo.fetch(revision_id=stop_revision, include_tags=fetch_tags)
1822+ return refs, stop_revision
1823+
1824+ def pull(self, stop_revision=None, overwrite=False,
1825+ possible_transports=None, run_hooks=True, local=False):
1826+ # This type of branch can't be bound.
1827+ if local:
1828+ raise errors.LocalRequiresBoundBranch()
1829+ if overwrite is True:
1830+ overwrite = set(["history", "tags"])
1831+ else:
1832+ overwrite = set()
1833+
1834+ result = GitPullResult()
1835+ result.source_branch = self.source
1836+ result.target_branch = self.target
1837+ with self.target.lock_write(), self.source.lock_read():
1838+ result.old_revid = self.target.last_revision()
1839+ refs, stop_revision = self.update_refs(stop_revision)
1840+ self.target.generate_revision_history(stop_revision,
1841+ (result.old_revid if ("history" not in overwrite) else None),
1842+ other_branch=self.source)
1843+ tags_ret = self.source.tags.merge_to(self.target.tags,
1844+ overwrite=("tags" in overwrite),
1845+ source_tag_refs=remote_refs_dict_to_tag_refs(refs))
1846+ if isinstance(tags_ret, tuple):
1847+ (result.tag_updates, result.tag_conflicts) = tags_ret
1848+ else:
1849+ result.tag_conflicts = tags_ret
1850+ result.new_revid = self.target.last_revision()
1851+ result.local_branch = None
1852+ result.master_branch = result.target_branch
1853+ if run_hooks:
1854+ for hook in branch.Branch.hooks['post_pull']:
1855+ hook(result)
1856+ return result
1857+
1858+
1859+class InterToGitBranch(branch.GenericInterBranch):
1860+ """InterBranch implementation that pulls from a non-bzr into a Git branch."""
1861+
1862+ def __init__(self, source, target):
1863+ super(InterToGitBranch, self).__init__(source, target)
1864+ self.interrepo = _mod_repository.InterRepository.get(source.repository,
1865+ target.repository)
1866+
1867+ @staticmethod
1868+ def _get_branch_formats_to_test():
1869+ try:
1870+ default_format = branch.format_registry.get_default()
1871+ except AttributeError:
1872+ default_format = branch.BranchFormat._default_format
1873+ from .remote import RemoteGitBranchFormat
1874+ return [
1875+ (default_format, LocalGitBranchFormat()),
1876+ (default_format, RemoteGitBranchFormat())]
1877+
1878+ @classmethod
1879+ def is_compatible(self, source, target):
1880+ return (not isinstance(source, GitBranch) and
1881+ isinstance(target, GitBranch))
1882+
1883+ def _get_new_refs(self, stop_revision=None, fetch_tags=None):
1884+ if not self.source.is_locked():
1885+ raise errors.ObjectNotLocked(self.source)
1886+ if stop_revision is None:
1887+ (stop_revno, stop_revision) = self.source.last_revision_info()
1888+ else:
1889+ stop_revno = self.source.revision_id_to_revno(stop_revision)
1890+ if type(stop_revision) is not str:
1891+ raise TypeError(stop_revision)
1892+ main_ref = self.target.ref
1893+ refs = { main_ref: (None, stop_revision) }
1894+ if fetch_tags is None:
1895+ c = self.source.get_config_stack()
1896+ fetch_tags = c.get('branch.fetch_tags')
1897+ for name, revid in self.source.tags.get_tag_dict().iteritems():
1898+ if self.source.repository.has_revision(revid):
1899+ ref = tag_name_to_ref(name)
1900+ if not check_ref_format(ref):
1901+ warning("skipping tag with invalid characters %s (%s)",
1902+ name, ref)
1903+ continue
1904+ if fetch_tags:
1905+ # FIXME: Skip tags that are not in the ancestry
1906+ refs[ref] = (None, revid)
1907+ return refs, main_ref, (stop_revno, stop_revision)
1908+
1909+ def _update_refs(self, result, old_refs, new_refs, overwrite):
1910+ mutter("updating refs. old refs: %r, new refs: %r",
1911+ old_refs, new_refs)
1912+ result.tag_updates = {}
1913+ result.tag_conflicts = []
1914+ ret = dict(old_refs)
1915+ def ref_equals(refs, ref, git_sha, revid):
1916+ try:
1917+ value = refs[ref]
1918+ except KeyError:
1919+ return False
1920+ if (value[0] is not None and
1921+ git_sha is not None and
1922+ value[0] == git_sha):
1923+ return True
1924+ if (value[1] is not None and
1925+ revid is not None and
1926+ value[1] == revid):
1927+ return True
1928+ # FIXME: If one side only has the git sha available and the other only
1929+ # has the bzr revid, then this will cause us to show a tag as updated
1930+ # that hasn't actually been updated.
1931+ return False
1932+ # FIXME: Check for diverged branches
1933+ for ref, (git_sha, revid) in new_refs.iteritems():
1934+ if ref_equals(ret, ref, git_sha, revid):
1935+ # Already up to date
1936+ if git_sha is None:
1937+ git_sha = old_refs[ref][0]
1938+ if revid is None:
1939+ revid = old_refs[ref][1]
1940+ ret[ref] = new_refs[ref] = (git_sha, revid)
1941+ elif ref not in ret or overwrite:
1942+ try:
1943+ tag_name = ref_to_tag_name(ref)
1944+ except ValueError:
1945+ pass
1946+ else:
1947+ result.tag_updates[tag_name] = revid
1948+ ret[ref] = (git_sha, revid)
1949+ else:
1950+ # FIXME: Check diverged
1951+ diverged = False
1952+ if diverged:
1953+ try:
1954+ name = ref_to_tag_name(ref)
1955+ except ValueError:
1956+ pass
1957+ else:
1958+ result.tag_conflicts.append((name, revid, ret[name][1]))
1959+ else:
1960+ ret[ref] = (git_sha, revid)
1961+ return ret
1962+
1963+ def fetch(self, stop_revision=None, fetch_tags=None, lossy=False, limit=None):
1964+ if stop_revision is None:
1965+ stop_revision = self.source.last_revision()
1966+ ret = []
1967+ if fetch_tags:
1968+ for k, v in self.source.tags.get_tag_dict().iteritems():
1969+ ret.append((None, v))
1970+ ret.append((None, stop_revision))
1971+ try:
1972+ self.interrepo.fetch_objects(ret, lossy=lossy, limit=limit)
1973+ except NoPushSupport:
1974+ raise errors.NoRoundtrippingSupport(self.source, self.target)
1975+
1976+ def pull(self, overwrite=False, stop_revision=None, local=False,
1977+ possible_transports=None, run_hooks=True):
1978+ result = GitBranchPullResult()
1979+ result.source_branch = self.source
1980+ result.target_branch = self.target
1981+ with self.source.lock_read(), self.target.lock_write():
1982+ new_refs, main_ref, stop_revinfo = self._get_new_refs(
1983+ stop_revision)
1984+ def update_refs(old_refs):
1985+ return self._update_refs(result, old_refs, new_refs, overwrite)
1986+ try:
1987+ result.revidmap, old_refs, new_refs = self.interrepo.fetch_refs(
1988+ update_refs, lossy=False)
1989+ except NoPushSupport:
1990+ raise errors.NoRoundtrippingSupport(self.source, self.target)
1991+ (old_sha1, result.old_revid) = old_refs.get(main_ref, (ZERO_SHA, NULL_REVISION))
1992+ if result.old_revid is None:
1993+ result.old_revid = self.target.lookup_foreign_revision_id(old_sha1)
1994+ result.new_revid = new_refs[main_ref][1]
1995+ result.local_branch = None
1996+ result.master_branch = self.target
1997+ if run_hooks:
1998+ for hook in branch.Branch.hooks['post_pull']:
1999+ hook(result)
2000+ return result
2001+
2002+ def push(self, overwrite=False, stop_revision=None, lossy=False,
2003+ _override_hook_source_branch=None):
2004+ result = GitBranchPushResult()
2005+ result.source_branch = self.source
2006+ result.target_branch = self.target
2007+ result.local_branch = None
2008+ result.master_branch = result.target_branch
2009+ with self.source.lock_read(), self.target.lock_write():
2010+ new_refs, main_ref, stop_revinfo = self._get_new_refs(stop_revision)
2011+ def update_refs(old_refs):
2012+ return self._update_refs(result, old_refs, new_refs, overwrite)
2013+ try:
2014+ result.revidmap, old_refs, new_refs = self.interrepo.fetch_refs(
2015+ update_refs, lossy=lossy, overwrite=overwrite)
2016+ except NoPushSupport:
2017+ raise errors.NoRoundtrippingSupport(self.source, self.target)
2018+ (old_sha1, result.old_revid) = old_refs.get(main_ref, (ZERO_SHA, NULL_REVISION))
2019+ if result.old_revid is None:
2020+ result.old_revid = self.target.lookup_foreign_revision_id(old_sha1)
2021+ result.new_revid = new_refs[main_ref][1]
2022+ (result.new_original_revno, result.new_original_revid) = stop_revinfo
2023+ for hook in branch.Branch.hooks['post_push']:
2024+ hook(result)
2025+ return result
2026+
2027+
2028+branch.InterBranch.register_optimiser(InterGitLocalGitBranch)
2029+branch.InterBranch.register_optimiser(InterFromGitBranch)
2030+branch.InterBranch.register_optimiser(InterToGitBranch)
2031+branch.InterBranch.register_optimiser(InterLocalGitRemoteGitBranch)
2032
2033=== added file 'breezy/plugins/git/bzr-receive-pack'
2034--- breezy/plugins/git/bzr-receive-pack 1970-01-01 00:00:00 +0000
2035+++ breezy/plugins/git/bzr-receive-pack 2018-05-10 00:44:29 +0000
2036@@ -0,0 +1,15 @@
2037+#!/usr/bin/env python
2038+
2039+import breezy
2040+from breezy.plugin import load_plugins
2041+load_plugins()
2042+from breezy.plugins.git.server import BzrBackend
2043+from dulwich.server import ReceivePackHandler, serve_command
2044+import sys, os
2045+
2046+if len(sys.argv) < 2:
2047+ print >>sys.stderr, "usage: %s <git-dir>" % os.path.basename(sys.argv[0])
2048+ sys.exit(1)
2049+
2050+backend = BzrBackend(breezy.transport.get_transport("/"))
2051+sys.exit(serve_command(ReceivePackHandler, backend=backend))
2052
2053=== added file 'breezy/plugins/git/bzr-upload-pack'
2054--- breezy/plugins/git/bzr-upload-pack 1970-01-01 00:00:00 +0000
2055+++ breezy/plugins/git/bzr-upload-pack 2018-05-10 00:44:29 +0000
2056@@ -0,0 +1,15 @@
2057+#!/usr/bin/env python
2058+
2059+import breezy
2060+from breezy.plugin import load_plugins
2061+load_plugins ()
2062+from breezy.plugins.git.server import BzrBackend
2063+from dulwich.server import UploadPackHandler, serve_command
2064+import sys, os
2065+
2066+if len(sys.argv) < 2:
2067+ print "usage: %s <git-dir>" % os.path.basename(sys.argv[0])
2068+ sys.exit(1)
2069+
2070+backend = BzrBackend(breezy.transport.get_transport("/"))
2071+sys.exit(serve_command(UploadPackHandler, backend=backend))
2072
2073=== added file 'breezy/plugins/git/cache.py'
2074--- breezy/plugins/git/cache.py 1970-01-01 00:00:00 +0000
2075+++ breezy/plugins/git/cache.py 2018-05-10 00:44:29 +0000
2076@@ -0,0 +1,1008 @@
2077+# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
2078+#
2079+# This program is free software; you can redistribute it and/or modify
2080+# it under the terms of the GNU General Public License as published by
2081+# the Free Software Foundation; either version 2 of the License, or
2082+# (at your option) any later version.
2083+#
2084+# This program is distributed in the hope that it will be useful,
2085+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2086+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2087+# GNU General Public License for more details.
2088+#
2089+# You should have received a copy of the GNU General Public License
2090+# along with this program; if not, write to the Free Software
2091+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2092+
2093+"""Map from Git sha's to Bazaar objects."""
2094+
2095+from __future__ import absolute_import
2096+
2097+from dulwich.objects import (
2098+ sha_to_hex,
2099+ hex_to_sha,
2100+ )
2101+import os
2102+import threading
2103+
2104+from dulwich.objects import (
2105+ ShaFile,
2106+ )
2107+
2108+from ... import (
2109+ errors as bzr_errors,
2110+ osutils,
2111+ registry,
2112+ trace,
2113+ )
2114+from ...bzr import (
2115+ btree_index as _mod_btree_index,
2116+ index as _mod_index,
2117+ versionedfile,
2118+ )
2119+from ...transport import (
2120+ get_transport,
2121+ )
2122+
2123+
2124+def get_cache_dir():
2125+ try:
2126+ from xdg.BaseDirectory import xdg_cache_home
2127+ except ImportError:
2128+ from ...config import config_dir
2129+ ret = os.path.join(config_dir(), "git")
2130+ else:
2131+ ret = os.path.join(xdg_cache_home, "breezy", "git")
2132+ if not os.path.isdir(ret):
2133+ os.makedirs(ret)
2134+ return ret
2135+
2136+
2137+def get_remote_cache_transport(repository):
2138+ """Retrieve the transport to use when accessing (unwritable) remote
2139+ repositories.
2140+ """
2141+ uuid = getattr(repository, "uuid", None)
2142+ if uuid is None:
2143+ path = get_cache_dir()
2144+ else:
2145+ path = os.path.join(get_cache_dir(), uuid)
2146+ if not os.path.isdir(path):
2147+ os.mkdir(path)
2148+ return get_transport(path)
2149+
2150+
2151+def check_pysqlite_version(sqlite3):
2152+ """Check that sqlite library is compatible.
2153+
2154+ """
2155+ if (sqlite3.sqlite_version_info[0] < 3 or
2156+ (sqlite3.sqlite_version_info[0] == 3 and
2157+ sqlite3.sqlite_version_info[1] < 3)):
2158+ trace.warning('Needs at least sqlite 3.3.x')
2159+ raise bzr_errors.BzrError("incompatible sqlite library")
2160+
2161+try:
2162+ try:
2163+ import sqlite3
2164+ check_pysqlite_version(sqlite3)
2165+ except (ImportError, bzr_errors.BzrError), e:
2166+ from pysqlite2 import dbapi2 as sqlite3
2167+ check_pysqlite_version(sqlite3)
2168+except:
2169+ trace.warning('Needs at least Python2.5 or Python2.4 with the pysqlite2 '
2170+ 'module')
2171+ raise bzr_errors.BzrError("missing sqlite library")
2172+
2173+
2174+_mapdbs = threading.local()
2175+def mapdbs():
2176+ """Get a cache for this thread's db connections."""
2177+ try:
2178+ return _mapdbs.cache
2179+ except AttributeError:
2180+ _mapdbs.cache = {}
2181+ return _mapdbs.cache
2182+
2183+
2184+class GitShaMap(object):
2185+ """Git<->Bzr revision id mapping database."""
2186+
2187+ def lookup_git_sha(self, sha):
2188+ """Lookup a Git sha in the database.
2189+ :param sha: Git object sha
2190+ :return: list with (type, type_data) tuples with type_data:
2191+ commit: revid, tree_sha, verifiers
2192+ blob: fileid, revid
2193+ tree: fileid, revid
2194+ """
2195+ raise NotImplementedError(self.lookup_git_sha)
2196+
2197+ def lookup_blob_id(self, file_id, revision):
2198+ """Retrieve a Git blob SHA by file id.
2199+
2200+ :param file_id: File id of the file/symlink
2201+ :param revision: revision in which the file was last changed.
2202+ """
2203+ raise NotImplementedError(self.lookup_blob_id)
2204+
2205+ def lookup_tree_id(self, file_id, revision):
2206+ """Retrieve a Git tree SHA by file id.
2207+ """
2208+ raise NotImplementedError(self.lookup_tree_id)
2209+
2210+ def lookup_commit(self, revid):
2211+ """Retrieve a Git commit SHA by Bazaar revision id.
2212+ """
2213+ raise NotImplementedError(self.lookup_commit)
2214+
2215+ def revids(self):
2216+ """List the revision ids known."""
2217+ raise NotImplementedError(self.revids)
2218+
2219+ def missing_revisions(self, revids):
2220+ """Return set of all the revisions that are not present."""
2221+ present_revids = set(self.revids())
2222+ if not isinstance(revids, set):
2223+ revids = set(revids)
2224+ return revids - present_revids
2225+
2226+ def sha1s(self):
2227+ """List the SHA1s."""
2228+ raise NotImplementedError(self.sha1s)
2229+
2230+ def start_write_group(self):
2231+ """Start writing changes."""
2232+
2233+ def commit_write_group(self):
2234+ """Commit any pending changes."""
2235+
2236+ def abort_write_group(self):
2237+ """Abort any pending changes."""
2238+
2239+
2240+class ContentCache(object):
2241+ """Object that can cache Git objects."""
2242+
2243+ def add(self, object):
2244+ """Add an object."""
2245+ raise NotImplementedError(self.add)
2246+
2247+ def add_multi(self, objects):
2248+ """Add multiple objects."""
2249+ for obj in objects:
2250+ self.add(obj)
2251+
2252+ def __getitem__(self, sha):
2253+ """Retrieve an item, by SHA."""
2254+ raise NotImplementedError(self.__getitem__)
2255+
2256+
2257+class BzrGitCacheFormat(object):
2258+ """Bazaar-Git Cache Format."""
2259+
2260+ def get_format_string(self):
2261+ """Return a single-line unique format string for this cache format."""
2262+ raise NotImplementedError(self.get_format_string)
2263+
2264+ def open(self, transport):
2265+ """Open this format on a transport."""
2266+ raise NotImplementedError(self.open)
2267+
2268+ def initialize(self, transport):
2269+ """Create a new instance of this cache format at transport."""
2270+ transport.put_bytes('format', self.get_format_string())
2271+
2272+ @classmethod
2273+ def from_transport(self, transport):
2274+ """Open a cache file present on a transport, or initialize one.
2275+
2276+ :param transport: Transport to use
2277+ :return: A BzrGitCache instance
2278+ """
2279+ try:
2280+ format_name = transport.get_bytes('format')
2281+ format = formats.get(format_name)
2282+ except bzr_errors.NoSuchFile:
2283+ format = formats.get('default')
2284+ format.initialize(transport)
2285+ return format.open(transport)
2286+
2287+ @classmethod
2288+ def from_repository(cls, repository):
2289+ """Open a cache file for a repository.
2290+
2291+ This will use the repository's transport to store the cache file, or
2292+ use the users global cache directory if the repository has no
2293+ transport associated with it.
2294+
2295+ :param repository: Repository to open the cache for
2296+ :return: A `BzrGitCache`
2297+ """
2298+ from ...transport.local import LocalTransport
2299+ repo_transport = getattr(repository, "_transport", None)
2300+ if (repo_transport is not None and
2301+ isinstance(repo_transport, LocalTransport)):
2302+ # Even if we don't write to this repo, we should be able
2303+ # to update its cache.
2304+ try:
2305+ repo_transport = remove_readonly_transport_decorator(repo_transport)
2306+ except bzr_errors.ReadOnlyError:
2307+ transport = None
2308+ else:
2309+ try:
2310+ repo_transport.mkdir('git')
2311+ except bzr_errors.FileExists:
2312+ pass
2313+ transport = repo_transport.clone('git')
2314+ else:
2315+ transport = None
2316+ if transport is None:
2317+ transport = get_remote_cache_transport(repository)
2318+ return cls.from_transport(transport)
2319+
2320+
2321+class CacheUpdater(object):
2322+ """Base class for objects that can update a bzr-git cache."""
2323+
2324+ def add_object(self, obj, bzr_key_data, path):
2325+ """Add an object.
2326+
2327+ :param obj: Object type ("commit", "blob" or "tree")
2328+ :param bzr_key_data: bzr key store data or testament_sha in case
2329+ of commit
2330+ :param path: Path of the object (optional)
2331+ """
2332+ raise NotImplementedError(self.add_object)
2333+
2334+ def finish(self):
2335+ raise NotImplementedError(self.finish)
2336+
2337+
2338+class BzrGitCache(object):
2339+ """Caching backend."""
2340+
2341+ def __init__(self, idmap, cache_updater_klass):
2342+ self.idmap = idmap
2343+ self._cache_updater_klass = cache_updater_klass
2344+
2345+ def get_updater(self, rev):
2346+ """Update an object that implements the CacheUpdater interface for
2347+ updating this cache.
2348+ """
2349+ return self._cache_updater_klass(self, rev)
2350+
2351+
2352+DictBzrGitCache = lambda: BzrGitCache(DictGitShaMap(), DictCacheUpdater)
2353+
2354+
2355+class DictCacheUpdater(CacheUpdater):
2356+ """Cache updater for dict-based caches."""
2357+
2358+ def __init__(self, cache, rev):
2359+ self.cache = cache
2360+ self.revid = rev.revision_id
2361+ self.parent_revids = rev.parent_ids
2362+ self._commit = None
2363+ self._entries = []
2364+
2365+ def add_object(self, obj, bzr_key_data, path):
2366+ if isinstance(obj, tuple):
2367+ (type_name, hexsha) = obj
2368+ else:
2369+ type_name = obj.type_name
2370+ hexsha = obj.id
2371+ if type_name == "commit":
2372+ self._commit = obj
2373+ if type(bzr_key_data) is not dict:
2374+ raise TypeError(bzr_key_data)
2375+ key = self.revid
2376+ type_data = (self.revid, self._commit.tree, bzr_key_data)
2377+ self.cache.idmap._by_revid[self.revid] = hexsha
2378+ elif type_name in ("blob", "tree"):
2379+ if bzr_key_data is not None:
2380+ key = type_data = bzr_key_data
2381+ self.cache.idmap._by_fileid.setdefault(type_data[1], {})[type_data[0]] = hexsha
2382+ else:
2383+ raise AssertionError
2384+ entry = (type_name, type_data)
2385+ self.cache.idmap._by_sha.setdefault(hexsha, {})[key] = entry
2386+
2387+ def finish(self):
2388+ if self._commit is None:
2389+ raise AssertionError("No commit object added")
2390+ return self._commit
2391+
2392+
2393+class DictGitShaMap(GitShaMap):
2394+ """Git SHA map that uses a dictionary."""
2395+
2396+ def __init__(self):
2397+ self._by_sha = {}
2398+ self._by_fileid = {}
2399+ self._by_revid = {}
2400+
2401+ def lookup_blob_id(self, fileid, revision):
2402+ return self._by_fileid[revision][fileid]
2403+
2404+ def lookup_git_sha(self, sha):
2405+ for entry in self._by_sha[sha].itervalues():
2406+ yield entry
2407+
2408+ def lookup_tree_id(self, fileid, revision):
2409+ return self._by_fileid[revision][fileid]
2410+
2411+ def lookup_commit(self, revid):
2412+ return self._by_revid[revid]
2413+
2414+ def revids(self):
2415+ for key, entries in self._by_sha.iteritems():
2416+ for (type, type_data) in entries.values():
2417+ if type == "commit":
2418+ yield type_data[0]
2419+
2420+ def sha1s(self):
2421+ return self._by_sha.iterkeys()
2422+
2423+
2424+class SqliteCacheUpdater(CacheUpdater):
2425+
2426+ def __init__(self, cache, rev):
2427+ self.cache = cache
2428+ self.db = self.cache.idmap.db
2429+ self.revid = rev.revision_id
2430+ self._commit = None
2431+ self._trees = []
2432+ self._blobs = []
2433+
2434+ def add_object(self, obj, bzr_key_data, path):
2435+ if isinstance(obj, tuple):
2436+ (type_name, hexsha) = obj
2437+ else:
2438+ type_name = obj.type_name
2439+ hexsha = obj.id
2440+ if type_name == "commit":
2441+ self._commit = obj
2442+ if type(bzr_key_data) is not dict:
2443+ raise TypeError(bzr_key_data)
2444+ self._testament3_sha1 = bzr_key_data.get("testament3-sha1")
2445+ elif type_name == "tree":
2446+ if bzr_key_data is not None:
2447+ self._trees.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
2448+ elif type_name == "blob":
2449+ if bzr_key_data is not None:
2450+ self._blobs.append((hexsha, bzr_key_data[0], bzr_key_data[1]))
2451+ else:
2452+ raise AssertionError
2453+
2454+ def finish(self):
2455+ if self._commit is None:
2456+ raise AssertionError("No commit object added")
2457+ self.db.executemany(
2458+ "replace into trees (sha1, fileid, revid) values (?, ?, ?)",
2459+ self._trees)
2460+ self.db.executemany(
2461+ "replace into blobs (sha1, fileid, revid) values (?, ?, ?)",
2462+ self._blobs)
2463+ self.db.execute(
2464+ "replace into commits (sha1, revid, tree_sha, testament3_sha1) values (?, ?, ?, ?)",
2465+ (self._commit.id, self.revid, self._commit.tree, self._testament3_sha1))
2466+ return self._commit
2467+
2468+
2469+SqliteBzrGitCache = lambda p: BzrGitCache(SqliteGitShaMap(p), SqliteCacheUpdater)
2470+
2471+
2472+class SqliteGitCacheFormat(BzrGitCacheFormat):
2473+
2474+ def get_format_string(self):
2475+ return 'bzr-git sha map version 1 using sqlite\n'
2476+
2477+ def open(self, transport):
2478+ try:
2479+ basepath = transport.local_abspath(".")
2480+ except bzr_errors.NotLocalUrl:
2481+ basepath = get_cache_dir()
2482+ return SqliteBzrGitCache(os.path.join(basepath, "idmap.db"))
2483+
2484+
2485+class SqliteGitShaMap(GitShaMap):
2486+ """Bazaar GIT Sha map that uses a sqlite database for storage."""
2487+
2488+ def __init__(self, path=None):
2489+ self.path = path
2490+ if path is None:
2491+ self.db = sqlite3.connect(":memory:")
2492+ else:
2493+ if not mapdbs().has_key(path):
2494+ mapdbs()[path] = sqlite3.connect(path)
2495+ self.db = mapdbs()[path]
2496+ self.db.text_factory = str
2497+ self.db.executescript("""
2498+ create table if not exists commits(
2499+ sha1 text not null check(length(sha1) == 40),
2500+ revid text not null,
2501+ tree_sha text not null check(length(tree_sha) == 40)
2502+ );
2503+ create index if not exists commit_sha1 on commits(sha1);
2504+ create unique index if not exists commit_revid on commits(revid);
2505+ create table if not exists blobs(
2506+ sha1 text not null check(length(sha1) == 40),
2507+ fileid text not null,
2508+ revid text not null
2509+ );
2510+ create index if not exists blobs_sha1 on blobs(sha1);
2511+ create unique index if not exists blobs_fileid_revid on blobs(fileid, revid);
2512+ create table if not exists trees(
2513+ sha1 text unique not null check(length(sha1) == 40),
2514+ fileid text not null,
2515+ revid text not null
2516+ );
2517+ create unique index if not exists trees_sha1 on trees(sha1);
2518+ create unique index if not exists trees_fileid_revid on trees(fileid, revid);
2519+""")
2520+ try:
2521+ self.db.executescript(
2522+ "ALTER TABLE commits ADD testament3_sha1 TEXT;")
2523+ except sqlite3.OperationalError:
2524+ pass # Column already exists.
2525+
2526+ def __repr__(self):
2527+ return "%s(%r)" % (self.__class__.__name__, self.path)
2528+
2529+ def lookup_commit(self, revid):
2530+ cursor = self.db.execute("select sha1 from commits where revid = ?",
2531+ (revid,))
2532+ row = cursor.fetchone()
2533+ if row is not None:
2534+ return row[0]
2535+ raise KeyError
2536+
2537+ def commit_write_group(self):
2538+ self.db.commit()
2539+
2540+ def lookup_blob_id(self, fileid, revision):
2541+ row = self.db.execute("select sha1 from blobs where fileid = ? and revid = ?", (fileid, revision)).fetchone()
2542+ if row is not None:
2543+ return row[0]
2544+ raise KeyError(fileid)
2545+
2546+ def lookup_tree_id(self, fileid, revision):
2547+ row = self.db.execute("select sha1 from trees where fileid = ? and revid = ?", (fileid, revision)).fetchone()
2548+ if row is not None:
2549+ return row[0]
2550+ raise KeyError(fileid)
2551+
2552+ def lookup_git_sha(self, sha):
2553+ """Lookup a Git sha in the database.
2554+
2555+ :param sha: Git object sha
2556+ :return: (type, type_data) with type_data:
2557+ commit: revid, tree sha, verifiers
2558+ tree: fileid, revid
2559+ blob: fileid, revid
2560+ """
2561+ found = False
2562+ cursor = self.db.execute("select revid, tree_sha, testament3_sha1 from commits where sha1 = ?", (sha,))
2563+ for row in cursor.fetchall():
2564+ found = True
2565+ if row[2] is not None:
2566+ verifiers = {"testament3-sha1": row[2]}
2567+ else:
2568+ verifiers = {}
2569+ yield ("commit", (row[0], row[1], verifiers))
2570+ cursor = self.db.execute("select fileid, revid from blobs where sha1 = ?", (sha,))
2571+ for row in cursor.fetchall():
2572+ found = True
2573+ yield ("blob", row)
2574+ cursor = self.db.execute("select fileid, revid from trees where sha1 = ?", (sha,))
2575+ for row in cursor.fetchall():
2576+ found = True
2577+ yield ("tree", row)
2578+ if not found:
2579+ raise KeyError(sha)
2580+
2581+ def revids(self):
2582+ """List the revision ids known."""
2583+ return (row for (row,) in self.db.execute("select revid from commits"))
2584+
2585+ def sha1s(self):
2586+ """List the SHA1s."""
2587+ for table in ("blobs", "commits", "trees"):
2588+ for (sha,) in self.db.execute("select sha1 from %s" % table):
2589+ yield sha
2590+
2591+
2592+class TdbCacheUpdater(CacheUpdater):
2593+ """Cache updater for tdb-based caches."""
2594+
2595+ def __init__(self, cache, rev):
2596+ self.cache = cache
2597+ self.db = cache.idmap.db
2598+ self.revid = rev.revision_id
2599+ self.parent_revids = rev.parent_ids
2600+ self._commit = None
2601+ self._entries = []
2602+
2603+ def add_object(self, obj, bzr_key_data, path):
2604+ if isinstance(obj, tuple):
2605+ (type_name, hexsha) = obj
2606+ sha = hex_to_sha(hexsha)
2607+ else:
2608+ type_name = obj.type_name
2609+ sha = obj.sha().digest()
2610+ if type_name == "commit":
2611+ self.db["commit\0" + self.revid] = "\0".join((sha, obj.tree))
2612+ if type(bzr_key_data) is not dict:
2613+ raise TypeError(bzr_key_data)
2614+ type_data = (self.revid, obj.tree)
2615+ try:
2616+ type_data += (bzr_key_data["testament3-sha1"],)
2617+ except KeyError:
2618+ pass
2619+ self._commit = obj
2620+ elif type_name == "blob":
2621+ if bzr_key_data is None:
2622+ return
2623+ self.db["\0".join(("blob", bzr_key_data[0], bzr_key_data[1]))] = sha
2624+ type_data = bzr_key_data
2625+ elif type_name == "tree":
2626+ if bzr_key_data is None:
2627+ return
2628+ type_data = bzr_key_data
2629+ else:
2630+ raise AssertionError
2631+ entry = "\0".join((type_name, ) + type_data) + "\n"
2632+ key = "git\0" + sha
2633+ try:
2634+ oldval = self.db[key]
2635+ except KeyError:
2636+ self.db[key] = entry
2637+ else:
2638+ if oldval[-1] != "\n":
2639+ self.db[key] = "".join([oldval, "\n", entry])
2640+ else:
2641+ self.db[key] = "".join([oldval, entry])
2642+
2643+ def finish(self):
2644+ if self._commit is None:
2645+ raise AssertionError("No commit object added")
2646+ return self._commit
2647+
2648+
2649+TdbBzrGitCache = lambda p: BzrGitCache(TdbGitShaMap(p), TdbCacheUpdater)
2650+
2651+
2652+class TdbGitCacheFormat(BzrGitCacheFormat):
2653+ """Cache format for tdb-based caches."""
2654+
2655+ def get_format_string(self):
2656+ return 'bzr-git sha map version 3 using tdb\n'
2657+
2658+ def open(self, transport):
2659+ try:
2660+ basepath = transport.local_abspath(".").encode(osutils._fs_enc)
2661+ except bzr_errors.NotLocalUrl:
2662+ basepath = get_cache_dir()
2663+ if type(basepath) is not str:
2664+ raise TypeError(basepath)
2665+ try:
2666+ return TdbBzrGitCache(os.path.join(basepath, "idmap.tdb"))
2667+ except ImportError:
2668+ raise ImportError(
2669+ "Unable to open existing bzr-git cache because 'tdb' is not "
2670+ "installed.")
2671+
2672+
2673+class TdbGitShaMap(GitShaMap):
2674+ """SHA Map that uses a TDB database.
2675+
2676+ Entries:
2677+
2678+ "git <sha1>" -> "<type> <type-data1> <type-data2>"
2679+ "commit revid" -> "<sha1> <tree-id>"
2680+ "tree fileid revid" -> "<sha1>"
2681+ "blob fileid revid" -> "<sha1>"
2682+ """
2683+
2684+ TDB_MAP_VERSION = 3
2685+ TDB_HASH_SIZE = 50000
2686+
2687+ def __init__(self, path=None):
2688+ import tdb
2689+ self.path = path
2690+ if path is None:
2691+ self.db = {}
2692+ else:
2693+ if type(path) is not str:
2694+ raise TypeError(path)
2695+ if not mapdbs().has_key(path):
2696+ mapdbs()[path] = tdb.Tdb(path, self.TDB_HASH_SIZE, tdb.DEFAULT,
2697+ os.O_RDWR|os.O_CREAT)
2698+ self.db = mapdbs()[path]
2699+ try:
2700+ if int(self.db["version"]) not in (2, 3):
2701+ trace.warning("SHA Map is incompatible (%s -> %d), rebuilding database.",
2702+ self.db["version"], self.TDB_MAP_VERSION)
2703+ self.db.clear()
2704+ except KeyError:
2705+ pass
2706+ self.db["version"] = str(self.TDB_MAP_VERSION)
2707+
2708+ def start_write_group(self):
2709+ """Start writing changes."""
2710+ self.db.transaction_start()
2711+
2712+ def commit_write_group(self):
2713+ """Commit any pending changes."""
2714+ self.db.transaction_commit()
2715+
2716+ def abort_write_group(self):
2717+ """Abort any pending changes."""
2718+ self.db.transaction_cancel()
2719+
2720+ def __repr__(self):
2721+ return "%s(%r)" % (self.__class__.__name__, self.path)
2722+
2723+ def lookup_commit(self, revid):
2724+ try:
2725+ return sha_to_hex(self.db["commit\0" + revid][:20])
2726+ except KeyError:
2727+ raise KeyError("No cache entry for %r" % revid)
2728+
2729+ def lookup_blob_id(self, fileid, revision):
2730+ return sha_to_hex(self.db["\0".join(("blob", fileid, revision))])
2731+
2732+ def lookup_git_sha(self, sha):
2733+ """Lookup a Git sha in the database.
2734+
2735+ :param sha: Git object sha
2736+ :return: (type, type_data) with type_data:
2737+ commit: revid, tree sha
2738+ blob: fileid, revid
2739+ tree: fileid, revid
2740+ """
2741+ if len(sha) == 40:
2742+ sha = hex_to_sha(sha)
2743+ value = self.db["git\0" + sha]
2744+ for data in value.splitlines():
2745+ data = data.split("\0")
2746+ if data[0] == "commit":
2747+ if len(data) == 3:
2748+ yield (data[0], (data[1], data[2], {}))
2749+ else:
2750+ yield (data[0], (data[1], data[2], {"testament3-sha1": data[3]}))
2751+ elif data[0] in ("tree", "blob"):
2752+ yield (data[0], tuple(data[1:]))
2753+ else:
2754+ raise AssertionError("unknown type %r" % data[0])
2755+
2756+ def missing_revisions(self, revids):
2757+ ret = set()
2758+ for revid in revids:
2759+ if self.db.get("commit\0" + revid) is None:
2760+ ret.add(revid)
2761+ return ret
2762+
2763+ def revids(self):
2764+ """List the revision ids known."""
2765+ for key in self.db.iterkeys():
2766+ if key.startswith("commit\0"):
2767+ yield key[7:]
2768+
2769+ def sha1s(self):
2770+ """List the SHA1s."""
2771+ for key in self.db.iterkeys():
2772+ if key.startswith("git\0"):
2773+ yield sha_to_hex(key[4:])
2774+
2775+
2776+class VersionedFilesContentCache(ContentCache):
2777+
2778+ def __init__(self, vf):
2779+ self._vf = vf
2780+
2781+ def add(self, obj):
2782+ self._vf.insert_record_stream(
2783+ [versionedfile.ChunkedContentFactory((obj.id,), [], None,
2784+ obj.as_legacy_object_chunks())])
2785+
2786+ def __getitem__(self, sha):
2787+ stream = self._vf.get_record_stream([(sha,)], 'unordered', True)
2788+ entry = stream.next()
2789+ if entry.storage_kind == 'absent':
2790+ raise KeyError(sha)
2791+ return ShaFile._parse_legacy_object(entry.get_bytes_as('fulltext'))
2792+
2793+
2794+class IndexCacheUpdater(CacheUpdater):
2795+
2796+ def __init__(self, cache, rev):
2797+ self.cache = cache
2798+ self.revid = rev.revision_id
2799+ self.parent_revids = rev.parent_ids
2800+ self._commit = None
2801+ self._entries = []
2802+
2803+ def add_object(self, obj, bzr_key_data, path):
2804+ if isinstance(obj, tuple):
2805+ (type_name, hexsha) = obj
2806+ else:
2807+ type_name = obj.type_name
2808+ hexsha = obj.id
2809+ if type_name == "commit":
2810+ self._commit = obj
2811+ if type(bzr_key_data) is not dict:
2812+ raise TypeError(bzr_key_data)
2813+ self.cache.idmap._add_git_sha(hexsha, "commit",
2814+ (self.revid, obj.tree, bzr_key_data))
2815+ self.cache.idmap._add_node(("commit", self.revid, "X"),
2816+ " ".join((hexsha, obj.tree)))
2817+ elif type_name == "blob":
2818+ self.cache.idmap._add_git_sha(hexsha, "blob", bzr_key_data)
2819+ self.cache.idmap._add_node(("blob", bzr_key_data[0],
2820+ bzr_key_data[1]), hexsha)
2821+ elif type_name == "tree":
2822+ self.cache.idmap._add_git_sha(hexsha, "tree", bzr_key_data)
2823+ else:
2824+ raise AssertionError
2825+
2826+ def finish(self):
2827+ return self._commit
2828+
2829+
2830+class IndexBzrGitCache(BzrGitCache):
2831+
2832+ def __init__(self, transport=None):
2833+ mapper = versionedfile.ConstantMapper("trees")
2834+ shamap = IndexGitShaMap(transport.clone('index'))
2835+ from .transportgit import TransportObjectStore
2836+ super(IndexBzrGitCache, self).__init__(shamap, IndexCacheUpdater)
2837+
2838+
2839+class IndexGitCacheFormat(BzrGitCacheFormat):
2840+
2841+ def get_format_string(self):
2842+ return 'bzr-git sha map with git object cache version 1\n'
2843+
2844+ def initialize(self, transport):
2845+ super(IndexGitCacheFormat, self).initialize(transport)
2846+ transport.mkdir('index')
2847+ transport.mkdir('objects')
2848+ from .transportgit import TransportObjectStore
2849+ TransportObjectStore.init(transport.clone('objects'))
2850+
2851+ def open(self, transport):
2852+ return IndexBzrGitCache(transport)
2853+
2854+
2855+class IndexGitShaMap(GitShaMap):
2856+ """SHA Map that uses the Bazaar APIs to store a cache.
2857+
2858+ BTree Index file with the following contents:
2859+
2860+ ("git", <sha1>, "X") -> "<type> <type-data1> <type-data2>"
2861+ ("commit", <revid>, "X") -> "<sha1> <tree-id>"
2862+ ("blob", <fileid>, <revid>) -> <sha1>
2863+
2864+ """
2865+
2866+ def __init__(self, transport=None):
2867+ if transport is None:
2868+ self._transport = None
2869+ self._index = _mod_index.InMemoryGraphIndex(0, key_elements=3)
2870+ self._builder = self._index
2871+ else:
2872+ self._builder = None
2873+ self._transport = transport
2874+ self._index = _mod_index.CombinedGraphIndex([])
2875+ for name in self._transport.list_dir("."):
2876+ if not name.endswith(".rix"):
2877+ continue
2878+ x = _mod_btree_index.BTreeGraphIndex(self._transport, name,
2879+ self._transport.stat(name).st_size)
2880+ self._index.insert_index(0, x)
2881+
2882+ @classmethod
2883+ def from_repository(cls, repository):
2884+ transport = getattr(repository, "_transport", None)
2885+ if transport is not None:
2886+ try:
2887+ transport.mkdir('git')
2888+ except bzr_errors.FileExists:
2889+ pass
2890+ return cls(transport.clone('git'))
2891+ from ...transport import get_transport
2892+ return cls(get_transport(get_cache_dir()))
2893+
2894+ def __repr__(self):
2895+ if self._transport is not None:
2896+ return "%s(%r)" % (self.__class__.__name__, self._transport.base)
2897+ else:
2898+ return "%s()" % (self.__class__.__name__)
2899+
2900+ def repack(self):
2901+ if self._builder is not None:
2902+ raise errors.BzrError('builder already open')
2903+ self.start_write_group()
2904+ self._builder.add_nodes(
2905+ ((key, value) for (_, key, value) in
2906+ self._index.iter_all_entries()))
2907+ to_remove = []
2908+ for name in self._transport.list_dir('.'):
2909+ if name.endswith('.rix'):
2910+ to_remove.append(name)
2911+ self.commit_write_group()
2912+ del self._index.indices[1:]
2913+ for name in to_remove:
2914+ self._transport.rename(name, name + '.old')
2915+
2916+ def start_write_group(self):
2917+ if self._builder is not None:
2918+ raise errors.BzrError('builder already open')
2919+ self._builder = _mod_btree_index.BTreeBuilder(0, key_elements=3)
2920+ self._name = osutils.sha()
2921+
2922+ def commit_write_group(self):
2923+ if self._builder is None:
2924+ raise errors.BzrError('builder not open')
2925+ stream = self._builder.finish()
2926+ name = self._name.hexdigest() + ".rix"
2927+ size = self._transport.put_file(name, stream)
2928+ index = _mod_btree_index.BTreeGraphIndex(self._transport, name, size)
2929+ self._index.insert_index(0, index)
2930+ self._builder = None
2931+ self._name = None
2932+
2933+ def abort_write_group(self):
2934+ if self._builder is None:
2935+ raise errors.BzrError('builder not open')
2936+ self._builder = None
2937+ self._name = None
2938+
2939+ def _add_node(self, key, value):
2940+ try:
2941+ self._get_entry(key)
2942+ except KeyError:
2943+ self._builder.add_node(key, value)
2944+ return False
2945+ else:
2946+ return True
2947+
2948+ def _get_entry(self, key):
2949+ entries = self._index.iter_entries([key])
2950+ try:
2951+ return entries.next()[2]
2952+ except StopIteration:
2953+ if self._builder is None:
2954+ raise KeyError
2955+ entries = self._builder.iter_entries([key])
2956+ try:
2957+ return entries.next()[2]
2958+ except StopIteration:
2959+ raise KeyError
2960+
2961+ def _iter_entries_prefix(self, prefix):
2962+ for entry in self._index.iter_entries_prefix([prefix]):
2963+ yield (entry[1], entry[2])
2964+ if self._builder is not None:
2965+ for entry in self._builder.iter_entries_prefix([prefix]):
2966+ yield (entry[1], entry[2])
2967+
2968+ def lookup_commit(self, revid):
2969+ return self._get_entry(("commit", revid, "X"))[:40]
2970+
2971+ def _add_git_sha(self, hexsha, type, type_data):
2972+ if hexsha is not None:
2973+ self._name.update(hexsha)
2974+ if type == "commit":
2975+ td = (type_data[0], type_data[1])
2976+ try:
2977+ td += (type_data[2]["testament3-sha1"],)
2978+ except KeyError:
2979+ pass
2980+ else:
2981+ td = type_data
2982+ self._add_node(("git", hexsha, "X"), " ".join((type,) + td))
2983+ else:
2984+ # This object is not represented in Git - perhaps an empty
2985+ # directory?
2986+ self._name.update(type + " ".join(type_data))
2987+
2988+ def lookup_blob_id(self, fileid, revision):
2989+ return self._get_entry(("blob", fileid, revision))
2990+
2991+ def lookup_git_sha(self, sha):
2992+ if len(sha) == 20:
2993+ sha = sha_to_hex(sha)
2994+ value = self._get_entry(("git", sha, "X"))
2995+ data = value.split(" ", 3)
2996+ if data[0] == "commit":
2997+ try:
2998+ if data[3]:
2999+ verifiers = {"testament3-sha1": data[3]}
3000+ else:
3001+ verifiers = {}
3002+ except IndexError:
3003+ verifiers = {}
3004+ yield ("commit", (data[1], data[2], verifiers))
3005+ else:
3006+ yield (data[0], tuple(data[1:]))
3007+
3008+ def revids(self):
3009+ """List the revision ids known."""
3010+ for key, value in self._iter_entries_prefix(("commit", None, None)):
3011+ yield key[1]
3012+
3013+ def missing_revisions(self, revids):
3014+ """Return set of all the revisions that are not present."""
3015+ missing_revids = set(revids)
3016+ for _, key, value in self._index.iter_entries((
3017+ ("commit", revid, "X") for revid in revids)):
3018+ missing_revids.remove(key[1])
3019+ return missing_revids
3020+
3021+ def sha1s(self):
3022+ """List the SHA1s."""
3023+ for key, value in self._iter_entries_prefix(("git", None, None)):
3024+ yield key[1]
3025+
3026+
3027+formats = registry.Registry()
3028+formats.register(TdbGitCacheFormat().get_format_string(),
3029+ TdbGitCacheFormat())
3030+formats.register(SqliteGitCacheFormat().get_format_string(),
3031+ SqliteGitCacheFormat())
3032+formats.register(IndexGitCacheFormat().get_format_string(),
3033+ IndexGitCacheFormat())
3034+# In the future, this will become the default:
3035+formats.register('default', IndexGitCacheFormat())
3036+
3037+
3038+
3039+def migrate_ancient_formats(repo_transport):
3040+ # Migrate older cache formats
3041+ repo_transport = remove_readonly_transport_decorator(repo_transport)
3042+ has_sqlite = repo_transport.has("git.db")
3043+ has_tdb = repo_transport.has("git.tdb")
3044+ if not has_sqlite or has_tdb:
3045+ return
3046+ try:
3047+ repo_transport.mkdir("git")
3048+ except bzr_errors.FileExists:
3049+ return
3050+ # Prefer migrating git.db over git.tdb, since the latter may not
3051+ # be openable on some platforms.
3052+ if has_sqlite:
3053+ SqliteGitCacheFormat().initialize(repo_transport.clone("git"))
3054+ repo_transport.rename("git.db", "git/idmap.db")
3055+ elif has_tdb:
3056+ TdbGitCacheFormat().initialize(repo_transport.clone("git"))
3057+ repo_transport.rename("git.tdb", "git/idmap.tdb")
3058+
3059+
3060+def remove_readonly_transport_decorator(transport):
3061+ if transport.is_readonly():
3062+ try:
3063+ return transport._decorated
3064+ except AttributeError:
3065+ raise bzr_errors.ReadOnlyError(transport)
3066+ return transport
3067+
3068+
3069+def from_repository(repository):
3070+ """Open a cache file for a repository.
3071+
3072+ If the repository is remote and there is no transport available from it
3073+ this will use a local file in the users cache directory
3074+ (typically ~/.cache/bazaar/git/)
3075+
3076+ :param repository: A repository object
3077+ """
3078+ repo_transport = getattr(repository, "_transport", None)
3079+ if repo_transport is not None:
3080+ try:
3081+ migrate_ancient_formats(repo_transport)
3082+ except bzr_errors.ReadOnlyError:
3083+ pass # Not much we can do
3084+ return BzrGitCacheFormat.from_repository(repository)
3085
3086=== added file 'breezy/plugins/git/commands.py'
3087--- breezy/plugins/git/commands.py 1970-01-01 00:00:00 +0000
3088+++ breezy/plugins/git/commands.py 2018-05-10 00:44:29 +0000
3089@@ -0,0 +1,340 @@
3090+# Copyright (C) 2006-2009 Canonical Ltd
3091+# Copyright (C) 2012-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3092+
3093+# Authors: Robert Collins <robert.collins@canonical.com>
3094+# Jelmer Vernooij <jelmer@samba.org>
3095+# John Carr <john.carr@unrouted.co.uk>
3096+#
3097+# This program is free software; you can redistribute it and/or modify
3098+# it under the terms of the GNU General Public License as published by
3099+# the Free Software Foundation; either version 2 of the License, or
3100+# (at your option) any later version.
3101+#
3102+# This program is distributed in the hope that it will be useful,
3103+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3104+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3105+# GNU General Public License for more details.
3106+#
3107+# You should have received a copy of the GNU General Public License
3108+# along with this program; if not, write to the Free Software
3109+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3110+
3111+"""Git-specific subcommands for Bazaar."""
3112+
3113+from __future__ import absolute_import
3114+
3115+import breezy.bzr.bzrdir
3116+from ...commands import (
3117+ Command,
3118+ display_command,
3119+ )
3120+from ...option import (
3121+ Option,
3122+ )
3123+
3124+
3125+class cmd_git_import(Command):
3126+ """Import all branches from a git repository.
3127+
3128+ """
3129+
3130+ takes_args = ["src_location", "dest_location?"]
3131+
3132+ takes_options = [
3133+ Option('colocated', help='Create colocated branches.'),
3134+ ]
3135+
3136+ def _get_colocated_branch(self, target_controldir, name):
3137+ from ...errors import NotBranchError
3138+ try:
3139+ return target_controldir.open_branch(name=name)
3140+ except NotBranchError:
3141+ return target_controldir.create_branch(name=name)
3142+
3143+ def _get_nested_branch(self, dest_transport, dest_format, name):
3144+ from ...controldir import ControlDir
3145+ from ...errors import NotBranchError
3146+ head_transport = dest_transport.clone(name)
3147+ try:
3148+ head_controldir = ControlDir.open_from_transport(head_transport)
3149+ except NotBranchError:
3150+ head_controldir = dest_format.initialize_on_transport_ex(
3151+ head_transport, create_prefix=True)[1]
3152+ try:
3153+ return head_controldir.open_branch()
3154+ except NotBranchError:
3155+ return head_controldir.create_branch()
3156+
3157+ def run(self, src_location, dest_location=None, colocated=False):
3158+ import os
3159+ import urllib
3160+ from ... import (
3161+ controldir,
3162+ trace,
3163+ ui,
3164+ urlutils,
3165+ )
3166+ from ...controldir import (
3167+ ControlDir,
3168+ )
3169+ from ...errors import (
3170+ BzrCommandError,
3171+ NoRepositoryPresent,
3172+ NotBranchError,
3173+ )
3174+ from . import gettext
3175+ from ...repository import (
3176+ InterRepository,
3177+ Repository,
3178+ )
3179+ from ...transport import get_transport
3180+ from .branch import (
3181+ LocalGitBranch,
3182+ )
3183+ from .refs import (
3184+ ref_to_branch_name,
3185+ )
3186+ from .repository import GitRepository
3187+
3188+ dest_format = controldir.ControlDirFormat.get_default_format()
3189+ if dest_format is None:
3190+ raise errors.BzrError('no default format')
3191+
3192+ if dest_location is None:
3193+ dest_location = os.path.basename(src_location.rstrip("/\\"))
3194+
3195+ dest_transport = get_transport(dest_location)
3196+
3197+ source_repo = Repository.open(src_location)
3198+ if not isinstance(source_repo, GitRepository):
3199+ raise BzrCommandError(gettext("%r is not a git repository") % src_location)
3200+ try:
3201+ target_controldir = ControlDir.open_from_transport(dest_transport)
3202+ except NotBranchError:
3203+ target_controldir = dest_format.initialize_on_transport_ex(
3204+ dest_transport, shared_repo=True)[1]
3205+ try:
3206+ target_repo = target_controldir.find_repository()
3207+ except NoRepositoryPresent:
3208+ target_repo = target_controldir.create_repository(shared=True)
3209+
3210+ if not target_repo.supports_rich_root():
3211+ raise BzrCommandError(gettext("Target repository doesn't support rich roots"))
3212+
3213+ interrepo = InterRepository.get(source_repo, target_repo)
3214+ mapping = source_repo.get_mapping()
3215+ refs = interrepo.fetch()
3216+ pb = ui.ui_factory.nested_progress_bar()
3217+ try:
3218+ for i, (name, sha) in enumerate(refs.iteritems()):
3219+ try:
3220+ branch_name = ref_to_branch_name(name)
3221+ except ValueError:
3222+ # Not a branch, ignore
3223+ continue
3224+ pb.update(gettext("creating branches"), i, len(refs))
3225+ if getattr(target_controldir._format, "colocated_branches", False) and colocated:
3226+ if name == "HEAD":
3227+ branch_name = None
3228+ head_branch = self._get_colocated_branch(target_controldir, branch_name)
3229+ else:
3230+ head_branch = self._get_nested_branch(dest_transport, dest_format, branch_name)
3231+ revid = mapping.revision_id_foreign_to_bzr(sha)
3232+ source_branch = LocalGitBranch(source_repo.controldir, source_repo,
3233+ sha)
3234+ if head_branch.last_revision() != revid:
3235+ head_branch.generate_revision_history(revid)
3236+ source_branch.tags.merge_to(head_branch.tags)
3237+ if not head_branch.get_parent():
3238+ url = urlutils.join_segment_parameters(
3239+ source_branch.base, {"branch": urllib.quote(branch_name.encode('utf-8'), '')})
3240+ head_branch.set_parent(url)
3241+ finally:
3242+ pb.finished()
3243+ trace.note(gettext(
3244+ "Use 'bzr checkout' to create a working tree in "
3245+ "the newly created branches."))
3246+
3247+
3248+class cmd_git_object(Command):
3249+ """List or display Git objects by SHA.
3250+
3251+ Cat a particular object's Git representation if a SHA is specified.
3252+ List all available SHAs otherwise.
3253+ """
3254+
3255+ hidden = True
3256+
3257+ aliases = ["git-objects", "git-cat"]
3258+ takes_args = ["sha1?"]
3259+ takes_options = [Option('directory',
3260+ short_name='d',
3261+ help='Location of repository.', type=unicode),
3262+ Option('pretty', help='Pretty-print objects.')]
3263+ encoding_type = 'exact'
3264+
3265+ @display_command
3266+ def run(self, sha1=None, directory=".", pretty=False):
3267+ from ...errors import (
3268+ BzrCommandError,
3269+ )
3270+ from ...controldir import (
3271+ ControlDir,
3272+ )
3273+ from .object_store import (
3274+ get_object_store,
3275+ )
3276+ from . import gettext
3277+ controldir, _ = ControlDir.open_containing(directory)
3278+ repo = controldir.find_repository()
3279+ object_store = get_object_store(repo)
3280+ with object_store.lock_read():
3281+ if sha1 is not None:
3282+ try:
3283+ obj = object_store[str(sha1)]
3284+ except KeyError:
3285+ raise BzrCommandError(gettext("Object not found: %s") % sha1)
3286+ if pretty:
3287+ text = obj.as_pretty_string()
3288+ else:
3289+ text = obj.as_raw_string()
3290+ self.outf.write(text)
3291+ else:
3292+ for sha1 in object_store:
3293+ self.outf.write("%s\n" % sha1)
3294+
3295+
3296+class cmd_git_refs(Command):
3297+ """Output all of the virtual refs for a repository.
3298+
3299+ """
3300+
3301+ hidden = True
3302+
3303+ takes_args = ["location?"]
3304+
3305+ @display_command
3306+ def run(self, location="."):
3307+ from ...controldir import (
3308+ ControlDir,
3309+ )
3310+ from .refs import (
3311+ get_refs_container,
3312+ )
3313+ from .object_store import (
3314+ get_object_store,
3315+ )
3316+ controldir, _ = ControlDir.open_containing(location)
3317+ repo = controldir.find_repository()
3318+ object_store = get_object_store(repo)
3319+ with object_store.lock_read():
3320+ refs = get_refs_container(controldir, object_store)
3321+ for k, v in refs.as_dict().iteritems():
3322+ self.outf.write("%s -> %s\n" % (k, v))
3323+
3324+
3325+class cmd_git_apply(Command):
3326+ """Apply a series of git-am style patches.
3327+
3328+ This command will in the future probably be integrated into
3329+ "bzr pull".
3330+ """
3331+
3332+ takes_options = [
3333+ Option('signoff', short_name='s', help='Add a Signed-off-by line.'),
3334+ Option('force',
3335+ help='Apply patches even if tree has uncommitted changes.')
3336+ ]
3337+ takes_args = ["patches*"]
3338+
3339+ def _apply_patch(self, wt, f, signoff):
3340+ """Apply a patch.
3341+
3342+ :param wt: A Bazaar working tree object.
3343+ :param f: Patch file to read.
3344+ :param signoff: Add Signed-Off-By flag.
3345+ """
3346+ from . import gettext
3347+ from ...errors import BzrCommandError
3348+ from dulwich.patch import git_am_patch_split
3349+ import subprocess
3350+ (c, diff, version) = git_am_patch_split(f)
3351+ # FIXME: Cope with git-specific bits in patch
3352+ # FIXME: Add new files to working tree
3353+ p = subprocess.Popen(["patch", "-p1"], stdin=subprocess.PIPE,
3354+ cwd=wt.basedir)
3355+ p.communicate(diff)
3356+ exitcode = p.wait()
3357+ if exitcode != 0:
3358+ raise BzrCommandError(gettext("error running patch"))
3359+ message = c.message
3360+ if signoff:
3361+ signed_off_by = wt.branch.get_config().username()
3362+ message += "Signed-off-by: %s\n" % signed_off_by.encode('utf-8')
3363+ wt.commit(authors=[c.author], message=message)
3364+
3365+ def run(self, patches_list=None, signoff=False, force=False):
3366+ from ...errors import UncommittedChanges
3367+ from ...workingtree import WorkingTree
3368+ if patches_list is None:
3369+ patches_list = []
3370+
3371+ tree, _ = WorkingTree.open_containing(".")
3372+ if tree.basis_tree().changes_from(tree).has_changed() and not force:
3373+ raise UncommittedChanges(tree)
3374+ with tree.lock_write():
3375+ for patch in patches_list:
3376+ with open(patch, 'r') as f:
3377+ self._apply_patch(tree, f, signoff=signoff)
3378+
3379+
3380+class cmd_git_push_pristine_tar_deltas(Command):
3381+ """Push pristine tar deltas to a git repository."""
3382+
3383+ takes_options = [Option('directory',
3384+ short_name='d',
3385+ help='Location of repository.', type=unicode)]
3386+ takes_args = ['target', 'package']
3387+
3388+ def run(self, target, package, directory='.'):
3389+ from ...branch import Branch
3390+ from ...errors import (
3391+ BzrCommandError,
3392+ NoSuchRevision,
3393+ )
3394+ from ...trace import warning
3395+ from ...repository import Repository
3396+ from .object_store import get_object_store
3397+ from .pristine_tar import (
3398+ revision_pristine_tar_data,
3399+ store_git_pristine_tar_data,
3400+ )
3401+ source = Branch.open_containing(directory)[0]
3402+ target_bzr = Repository.open(target)
3403+ target = getattr(target_bzr, '_git', None)
3404+ if target is None:
3405+ raise BzrCommandError("Target not a git repository")
3406+ git_store = get_object_store(source.repository)
3407+ with git_store.lock_read():
3408+ tag_dict = source.tags.get_tag_dict()
3409+ for name, revid in tag_dict.iteritems():
3410+ try:
3411+ rev = source.repository.get_revision(revid)
3412+ except NoSuchRevision:
3413+ continue
3414+ try:
3415+ delta, kind = revision_pristine_tar_data(rev)
3416+ except KeyError:
3417+ continue
3418+ gitid = git_store._lookup_revision_sha1(revid)
3419+ if not (name.startswith('upstream/') or name.startswith('upstream-')):
3420+ warning("Unexpected pristine tar revision tagged %s. Ignoring.",
3421+ name)
3422+ continue
3423+ upstream_version = name[len("upstream/"):]
3424+ filename = '%s_%s.orig.tar.%s' % (package, upstream_version, kind)
3425+ if not gitid in target:
3426+ warning("base git id %s for %s missing in target repository",
3427+ gitid, filename)
3428+ store_git_pristine_tar_data(target, filename.encode('utf-8'),
3429+ delta, gitid)
3430
3431=== added file 'breezy/plugins/git/commit.py'
3432--- breezy/plugins/git/commit.py 1970-01-01 00:00:00 +0000
3433+++ breezy/plugins/git/commit.py 2018-05-10 00:44:29 +0000
3434@@ -0,0 +1,241 @@
3435+# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3436+#
3437+# This program is free software; you can redistribute it and/or modify
3438+# it under the terms of the GNU General Public License as published by
3439+# the Free Software Foundation; either version 2 of the License, or
3440+# (at your option) any later version.
3441+#
3442+# This program is distributed in the hope that it will be useful,
3443+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3444+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3445+# GNU General Public License for more details.
3446+#
3447+# You should have received a copy of the GNU General Public License
3448+# along with this program; if not, write to the Free Software
3449+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3450+
3451+
3452+"""Support for committing in native Git working trees."""
3453+
3454+from __future__ import absolute_import
3455+
3456+from dulwich.index import (
3457+ commit_tree,
3458+ )
3459+import os
3460+import stat
3461+
3462+from ... import (
3463+ config as _mod_config,
3464+ gpg,
3465+ osutils,
3466+ revision as _mod_revision,
3467+ )
3468+from ...errors import (
3469+ BzrError,
3470+ RootMissing,
3471+ UnsupportedOperation,
3472+ )
3473+from ...repository import (
3474+ CommitBuilder,
3475+ )
3476+
3477+from dulwich.objects import (
3478+ S_IFGITLINK,
3479+ Blob,
3480+ Commit,
3481+ )
3482+from dulwich.index import read_submodule_head
3483+from dulwich.repo import Repo
3484+
3485+
3486+from .mapping import (
3487+ entry_mode,
3488+ object_mode,
3489+ fix_person_identifier,
3490+ )
3491+from .tree import entry_factory
3492+
3493+
3494+class SettingCustomFileIdsUnsupported(UnsupportedOperation):
3495+
3496+ _fmt = "Unable to store addition of file with custom file ids: %(file_ids)r"
3497+
3498+ def __init__(self, file_ids):
3499+ BzrError.__init__(self)
3500+ self.file_ids = file_ids
3501+
3502+
3503+class GitCommitBuilder(CommitBuilder):
3504+ """Commit builder for Git repositories."""
3505+
3506+ supports_record_entry_contents = False
3507+
3508+ def __init__(self, *args, **kwargs):
3509+ super(GitCommitBuilder, self).__init__(*args, **kwargs)
3510+ self.random_revid = True
3511+ self._validate_revprops(self._revprops)
3512+ self.store = self.repository._git.object_store
3513+ self._blobs = {}
3514+ self._inv_delta = []
3515+ self._any_changes = False
3516+ self._override_fileids = {}
3517+ self._mapping = self.repository.get_mapping()
3518+
3519+ def any_changes(self):
3520+ return self._any_changes
3521+
3522+ def record_iter_changes(self, workingtree, basis_revid, iter_changes):
3523+ seen_root = False
3524+ for (file_id, path, changed_content, versioned, parent, name, kind,
3525+ executable) in iter_changes:
3526+ if kind[1] in ("directory",):
3527+ self._inv_delta.append((path[0], path[1], file_id, entry_factory[kind[1]](file_id, name[1], parent[1])))
3528+ if kind[0] in ("file", "symlink"):
3529+ self._blobs[path[0].encode("utf-8")] = None
3530+ self._any_changes = True
3531+ if path[1] == "":
3532+ seen_root = True
3533+ continue
3534+ self._any_changes = True
3535+ if path[1] is None:
3536+ self._inv_delta.append((path[0], path[1], file_id, None))
3537+ self._blobs[path[0].encode("utf-8")] = None
3538+ continue
3539+ try:
3540+ entry_kls = entry_factory[kind[1]]
3541+ except KeyError:
3542+ raise KeyError("unknown kind %s" % kind[1])
3543+ entry = entry_kls(file_id, name[1], parent[1])
3544+ if kind[1] == "file":
3545+ entry.executable = executable[1]
3546+ blob = Blob()
3547+ f, st = workingtree.get_file_with_stat(path[1], file_id)
3548+ try:
3549+ blob.data = f.read()
3550+ finally:
3551+ f.close()
3552+ entry.text_size = len(blob.data)
3553+ entry.text_sha1 = osutils.sha_string(blob.data)
3554+ self.store.add_object(blob)
3555+ sha = blob.id
3556+ elif kind[1] == "symlink":
3557+ symlink_target = workingtree.get_symlink_target(path[1], file_id)
3558+ blob = Blob()
3559+ blob.data = symlink_target.encode("utf-8")
3560+ self.store.add_object(blob)
3561+ sha = blob.id
3562+ entry.symlink_target = symlink_target
3563+ st = None
3564+ elif kind[1] == "tree-reference":
3565+ sha = read_submodule_head(workingtree.abspath(path[1]))
3566+ reference_revision = workingtree.get_reference_revision(path[1], file_id)
3567+ entry.reference_revision = reference_revision
3568+ st = None
3569+ else:
3570+ raise AssertionError("Unknown kind %r" % kind[1])
3571+ mode = object_mode(kind[1], executable[1])
3572+ self._inv_delta.append((path[0], path[1], file_id, entry))
3573+ encoded_new_path = path[1].encode("utf-8")
3574+ self._blobs[encoded_new_path] = (mode, sha)
3575+ if st is not None:
3576+ yield file_id, path[1], (entry.text_sha1, st)
3577+ if self._mapping.generate_file_id(encoded_new_path) != file_id:
3578+ self._override_fileids[encoded_new_path] = file_id
3579+ else:
3580+ self._override_fileids[encoded_new_path] = None
3581+ if not seen_root and len(self.parents) == 0:
3582+ raise RootMissing()
3583+ if getattr(workingtree, "basis_tree", False):
3584+ basis_tree = workingtree.basis_tree()
3585+ else:
3586+ if len(self.parents) == 0:
3587+ basis_revid = _mod_revision.NULL_REVISION
3588+ else:
3589+ basis_revid = self.parents[0]
3590+ basis_tree = self.repository.revision_tree(basis_revid)
3591+ # Fill in entries that were not changed
3592+ for entry in basis_tree._iter_tree_contents(include_trees=False):
3593+ if entry.path in self._blobs:
3594+ continue
3595+ self._blobs[entry.path] = (entry.mode, entry.sha)
3596+ if not self._lossy:
3597+ try:
3598+ fileid_map = dict(basis_tree._fileid_map.file_ids)
3599+ except AttributeError:
3600+ fileid_map = {}
3601+ for path, file_id in self._override_fileids.iteritems():
3602+ if type(path) is not str:
3603+ raise TypeError(path)
3604+ if file_id is None:
3605+ if path in fileid_map:
3606+ del fileid_map[path]
3607+ else:
3608+ if type(file_id) is not str:
3609+ raise TypeError(file_id)
3610+ fileid_map[path] = file_id
3611+ if fileid_map:
3612+ fileid_blob = self._mapping.export_fileid_map(fileid_map)
3613+ else:
3614+ fileid_blob = None
3615+ if fileid_blob is not None:
3616+ if self._mapping.BZR_FILE_IDS_FILE is None:
3617+ raise SettingCustomFileIdsUnsupported(fileid_map)
3618+ self.store.add_object(fileid_blob)
3619+ self._blobs[self._mapping.BZR_FILE_IDS_FILE] = (stat.S_IFREG | 0644, fileid_blob.id)
3620+ else:
3621+ self._blobs[self._mapping.BZR_FILE_IDS_FILE] = None
3622+ self.new_inventory = None
3623+
3624+ def update_basis(self, tree):
3625+ # Nothing to do here
3626+ pass
3627+
3628+ def finish_inventory(self):
3629+ # eliminate blobs that were removed
3630+ for path, entry in iter(self._blobs.items()):
3631+ if entry is None:
3632+ del self._blobs[path]
3633+
3634+ def _iterblobs(self):
3635+ return ((path, sha, mode) for (path, (mode, sha)) in self._blobs.iteritems())
3636+
3637+ def commit(self, message):
3638+ self._validate_unicode_text(message, 'commit message')
3639+ c = Commit()
3640+ c.parents = [self.repository.lookup_bzr_revision_id(revid)[0] for revid in self.parents]
3641+ c.tree = commit_tree(self.store, self._iterblobs())
3642+ c.encoding = self._revprops.pop('git-explicit-encoding', 'utf-8')
3643+ c.committer = fix_person_identifier(self._committer.encode(c.encoding))
3644+ c.author = fix_person_identifier(self._revprops.pop('author', self._committer).encode(c.encoding))
3645+ if self._revprops:
3646+ raise NotImplementedError(self._revprops)
3647+ c.commit_time = int(self._timestamp)
3648+ c.author_time = int(self._timestamp)
3649+ c.commit_timezone = self._timezone
3650+ c.author_timezone = self._timezone
3651+ c.message = message.encode(c.encoding)
3652+ if self._config_stack.get('create_signatures') == _mod_config.SIGN_ALWAYS:
3653+ strategy = gpg.GPGStrategy(self._config_stack)
3654+ c.gpgsig = strategy.sign(c.as_raw_string(), gpg.MODE_DETACH)
3655+ self.store.add_object(c)
3656+ self.repository.commit_write_group()
3657+ self._new_revision_id = self._mapping.revision_id_foreign_to_bzr(c.id)
3658+ return self._new_revision_id
3659+
3660+ def abort(self):
3661+ if self.repository.is_in_write_group():
3662+ self.repository.abort_write_group()
3663+
3664+ def revision_tree(self):
3665+ return self.repository.revision_tree(self._new_revision_id)
3666+
3667+ def get_basis_delta(self):
3668+ # TODO(jelmer): remove this logic when lp:~jelmer/brz/remaining lands
3669+ for (oldpath, newpath, file_id, entry) in self._inv_delta:
3670+ if entry is not None:
3671+ entry.revision = self._new_revision_id
3672+ return self._inv_delta
3673+
3674+ def update_basis_by_delta(self, revid, delta):
3675+ pass
3676
3677=== added file 'breezy/plugins/git/config.py'
3678--- breezy/plugins/git/config.py 1970-01-01 00:00:00 +0000
3679+++ breezy/plugins/git/config.py 2018-05-10 00:44:29 +0000
3680@@ -0,0 +1,63 @@
3681+# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
3682+#
3683+# This program is free software; you can redistribute it and/or modify
3684+# it under the terms of the GNU General Public License as published by
3685+# the Free Software Foundation; either version 2 of the License, or
3686+# (at your option) any later version.
3687+#
3688+# This program is distributed in the hope that it will be useful,
3689+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3690+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3691+# GNU General Public License for more details.
3692+#
3693+# You should have received a copy of the GNU General Public License
3694+# along with this program; if not, write to the Free Software
3695+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3696+
3697+"""Config file handling for Git."""
3698+
3699+from __future__ import absolute_import
3700+
3701+from ... import (
3702+ config,
3703+ )
3704+
3705+class GitBranchConfig(config.BranchConfig):
3706+ """BranchConfig that uses locations.conf in place of branch.conf"""
3707+
3708+ def __init__(self, branch):
3709+ super(GitBranchConfig, self).__init__(branch)
3710+ # do not provide a BranchDataConfig
3711+ self.option_sources = self.option_sources[0], self.option_sources[2]
3712+
3713+ def __repr__(self):
3714+ return "<%s of %r>" % (self.__class__.__name__, self.branch)
3715+
3716+ def set_user_option(self, name, value, store=config.STORE_BRANCH,
3717+ warn_masked=False):
3718+ """Force local to True"""
3719+ config.BranchConfig.set_user_option(self, name, value,
3720+ store=config.STORE_LOCATION, warn_masked=warn_masked)
3721+
3722+ def _get_user_id(self):
3723+ # TODO: Read from ~/.gitconfig
3724+ return self._get_best_value('_get_user_id')
3725+
3726+
3727+class GitBranchStack(config._CompatibleStack):
3728+ """GitBranch stack."""
3729+
3730+ def __init__(self, branch):
3731+ lstore = config.LocationStore()
3732+ loc_matcher = config.LocationMatcher(lstore, branch.base)
3733+ # FIXME: This should also be looking in .git/config for
3734+ # local git branches.
3735+ gstore = config.GlobalStore()
3736+ super(GitBranchStack, self).__init__(
3737+ [self._get_overrides,
3738+ loc_matcher.get_sections,
3739+ gstore.get_sections],
3740+ # All modifications go to the corresponding section in
3741+ # locations.conf
3742+ lstore, branch.base)
3743+ self.branch = branch
3744
3745=== added file 'breezy/plugins/git/dir.py'
3746--- breezy/plugins/git/dir.py 1970-01-01 00:00:00 +0000
3747+++ breezy/plugins/git/dir.py 2018-05-10 00:44:29 +0000
3748@@ -0,0 +1,693 @@
3749+# Copyright (C) 2007 Canonical Ltd
3750+# Copyright (C) 2010-2018 Jelmer Vernooij
3751+#
3752+# This program is free software; you can redistribute it and/or modify
3753+# it under the terms of the GNU General Public License as published by
3754+# the Free Software Foundation; either version 2 of the License, or
3755+# (at your option) any later version.
3756+#
3757+# This program is distributed in the hope that it will be useful,
3758+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3759+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3760+# GNU General Public License for more details.
3761+#
3762+# You should have received a copy of the GNU General Public License
3763+# along with this program; if not, write to the Free Software
3764+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3765+
3766+"""An adapter between a Git control dir and a Bazaar ControlDir."""
3767+
3768+from __future__ import absolute_import
3769+
3770+import urllib
3771+
3772+from ... import (
3773+ branch as _mod_branch,
3774+ errors as bzr_errors,
3775+ trace,
3776+ osutils,
3777+ repository as _mod_repository,
3778+ revision as _mod_revision,
3779+ urlutils,
3780+ )
3781+from ...transport import (
3782+ do_catching_redirections,
3783+ get_transport_from_path,
3784+ )
3785+
3786+from ...controldir import (
3787+ BranchReferenceLoop,
3788+ ControlDir,
3789+ ControlDirFormat,
3790+ format_registry,
3791+ RepositoryAcquisitionPolicy,
3792+ )
3793+from .object_store import (
3794+ get_object_store,
3795+ )
3796+
3797+from .push import (
3798+ GitPushResult,
3799+ )
3800+from .transportgit import (
3801+ OBJECTDIR,
3802+ TransportObjectStore,
3803+ )
3804+
3805+
3806+class GitDirConfig(object):
3807+
3808+ def get_default_stack_on(self):
3809+ return None
3810+
3811+ def set_default_stack_on(self, value):
3812+ raise bzr_errors.BzrError("Cannot set configuration")
3813+
3814+
3815+class GitControlDirFormat(ControlDirFormat):
3816+
3817+ colocated_branches = True
3818+ fixed_components = True
3819+
3820+ def __eq__(self, other):
3821+ return type(self) == type(other)
3822+
3823+ def is_supported(self):
3824+ return True
3825+
3826+ def network_name(self):
3827+ return "git"
3828+
3829+
3830+class UseExistingRepository(RepositoryAcquisitionPolicy):
3831+ """A policy of reusing an existing repository"""
3832+
3833+ def __init__(self, repository, stack_on=None, stack_on_pwd=None,
3834+ require_stacking=False):
3835+ """Constructor.
3836+
3837+ :param repository: The repository to use.
3838+ :param stack_on: A location to stack on
3839+ :param stack_on_pwd: If stack_on is relative, the location it is
3840+ relative to.
3841+ """
3842+ super(UseExistingRepository, self).__init__(
3843+ stack_on, stack_on_pwd, require_stacking)
3844+ self._repository = repository
3845+
3846+ def acquire_repository(self, make_working_trees=None, shared=False,
3847+ possible_transports=None):
3848+ """Implementation of RepositoryAcquisitionPolicy.acquire_repository
3849+
3850+ Returns an existing repository to use.
3851+ """
3852+ return self._repository, False
3853+
3854+
3855+class GitDir(ControlDir):
3856+ """An adapter to the '.git' dir used by git."""
3857+
3858+ def is_supported(self):
3859+ return True
3860+
3861+ def can_convert_format(self):
3862+ return False
3863+
3864+ def break_lock(self):
3865+ # There are no global locks, so nothing to break.
3866+ raise NotImplementedError(self.break_lock)
3867+
3868+ def cloning_metadir(self, stacked=False):
3869+ return format_registry.make_controldir("git")
3870+
3871+ def checkout_metadir(self, stacked=False):
3872+ return format_registry.make_controldir("git")
3873+
3874+ def _get_selected_ref(self, branch, ref=None):
3875+ if ref is not None and branch is not None:
3876+ raise bzr_errors.BzrError("can't specify both ref and branch")
3877+ if ref is not None:
3878+ return ref
3879+ if branch is not None:
3880+ from .refs import branch_name_to_ref
3881+ return branch_name_to_ref(branch)
3882+ segment_parameters = getattr(
3883+ self.user_transport, "get_segment_parameters", lambda: {})()
3884+ ref = segment_parameters.get("ref")
3885+ if ref is not None:
3886+ return urlutils.unescape(ref)
3887+ if branch is None and getattr(self, "_get_selected_branch", False):
3888+ branch = self._get_selected_branch()
3889+ if branch is not None:
3890+ from .refs import branch_name_to_ref
3891+ return branch_name_to_ref(branch)
3892+ return b"HEAD"
3893+
3894+ def get_config(self):
3895+ return GitDirConfig()
3896+
3897+ def _available_backup_name(self, base):
3898+ return osutils.available_backup_name(base, self.root_transport.has)
3899+
3900+ def sprout(self, url, revision_id=None, force_new_repo=False,
3901+ recurse='down', possible_transports=None,
3902+ accelerator_tree=None, hardlink=False, stacked=False,
3903+ source_branch=None, create_tree_if_local=True):
3904+ from ...repository import InterRepository
3905+ from ...transport.local import LocalTransport
3906+ from ...transport import get_transport
3907+ target_transport = get_transport(url, possible_transports)
3908+ target_transport.ensure_base()
3909+ cloning_format = self.cloning_metadir()
3910+ # Create/update the result branch
3911+ try:
3912+ result = ControlDir.open_from_transport(target_transport)
3913+ except bzr_errors.NotBranchError:
3914+ result = cloning_format.initialize_on_transport(target_transport)
3915+ source_branch = self.open_branch()
3916+ source_repository = self.find_repository()
3917+ try:
3918+ result_repo = result.find_repository()
3919+ except bzr_errors.NoRepositoryPresent:
3920+ result_repo = result.create_repository()
3921+ target_is_empty = True
3922+ else:
3923+ target_is_empty = None # Unknown
3924+ if stacked:
3925+ raise _mod_branch.UnstackableBranchFormat(self._format, self.user_url)
3926+ interrepo = InterRepository.get(source_repository, result_repo)
3927+
3928+ if revision_id is not None:
3929+ determine_wants = interrepo.get_determine_wants_revids(
3930+ [revision_id], include_tags=True)
3931+ else:
3932+ determine_wants = interrepo.determine_wants_all
3933+ interrepo.fetch_objects(determine_wants=determine_wants,
3934+ mapping=source_branch.mapping)
3935+ result_branch = source_branch.sprout(result,
3936+ revision_id=revision_id, repository=result_repo)
3937+ if (create_tree_if_local
3938+ and isinstance(target_transport, LocalTransport)
3939+ and (result_repo is None or result_repo.make_working_trees())):
3940+ wt = result.create_workingtree(accelerator_tree=accelerator_tree,
3941+ hardlink=hardlink, from_branch=result_branch)
3942+ return result
3943+
3944+ def clone_on_transport(self, transport, revision_id=None,
3945+ force_new_repo=False, preserve_stacking=False, stacked_on=None,
3946+ create_prefix=False, use_existing_dir=True, no_tree=False):
3947+ """See ControlDir.clone_on_transport."""
3948+ from ...repository import InterRepository
3949+ from .mapping import default_mapping
3950+ if stacked_on is not None:
3951+ raise _mod_branch.UnstackableBranchFormat(self._format, self.user_url)
3952+ if no_tree:
3953+ format = BareLocalGitControlDirFormat()
3954+ else:
3955+ format = LocalGitControlDirFormat()
3956+ (target_repo, target_controldir, stacking,
3957+ repo_policy) = format.initialize_on_transport_ex(
3958+ transport, use_existing_dir=use_existing_dir,
3959+ create_prefix=create_prefix,
3960+ force_new_repo=force_new_repo)
3961+ target_repo = target_controldir.find_repository()
3962+ target_git_repo = target_repo._git
3963+ source_repo = self.find_repository()
3964+ source_git_repo = source_repo._git
3965+ interrepo = InterRepository.get(source_repo, target_repo)
3966+ if revision_id is not None:
3967+ determine_wants = interrepo.get_determine_wants_revids([revision_id], include_tags=True)
3968+ else:
3969+ determine_wants = interrepo.determine_wants_all
3970+ (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
3971+ mapping=default_mapping)
3972+ for name, val in refs.iteritems():
3973+ target_git_repo.refs[name] = val
3974+ result_dir = self.__class__(transport, target_git_repo, format)
3975+ if revision_id is not None:
3976+ result_dir.open_branch().set_last_revision(revision_id)
3977+ try:
3978+ # Cheaper to check if the target is not local, than to try making
3979+ # the tree and fail.
3980+ result_dir.root_transport.local_abspath('.')
3981+ if result_dir.open_repository().make_working_trees():
3982+ self.open_workingtree().clone(result_dir, revision_id=revision_id)
3983+ except (bzr_errors.NoWorkingTree, bzr_errors.NotLocalUrl):
3984+ pass
3985+
3986+ return result_dir
3987+
3988+ def find_repository(self):
3989+ """Find the repository that should be used.
3990+
3991+ This does not require a branch as we use it to find the repo for
3992+ new branches as well as to hook existing branches up to their
3993+ repository.
3994+ """
3995+ return self._gitrepository_class(self._find_commondir())
3996+
3997+ def get_refs_container(self):
3998+ """Retrieve the refs container.
3999+ """
4000+ raise NotImplementedError(self.get_refs_container)
4001+
4002+ def determine_repository_policy(self, force_new_repo=False, stack_on=None,
4003+ stack_on_pwd=None, require_stacking=False):
4004+ """Return an object representing a policy to use.
4005+
4006+ This controls whether a new repository is created, and the format of
4007+ that repository, or some existing shared repository used instead.
4008+
4009+ If stack_on is supplied, will not seek a containing shared repo.
4010+
4011+ :param force_new_repo: If True, require a new repository to be created.
4012+ :param stack_on: If supplied, the location to stack on. If not
4013+ supplied, a default_stack_on location may be used.
4014+ :param stack_on_pwd: If stack_on is relative, the location it is
4015+ relative to.
4016+ """
4017+ return UseExistingRepository(self.find_repository())
4018+
4019+ def get_branches(self):
4020+ from .refs import ref_to_branch_name
4021+ ret = {}
4022+ for ref in self.get_refs_container().keys():
4023+ try:
4024+ branch_name = ref_to_branch_name(ref)
4025+ except ValueError:
4026+ continue
4027+ except UnicodeDecodeError:
4028+ trace.warning("Ignoring branch %r with unicode error ref", ref)
4029+ continue
4030+ ret[branch_name] = self.open_branch(ref=ref)
4031+ return ret
4032+
4033+ def list_branches(self):
4034+ return self.get_branches().values()
4035+
4036+ def push_branch(self, source, revision_id=None, overwrite=False,
4037+ remember=False, create_prefix=False, lossy=False,
4038+ name=None):
4039+ """Push the source branch into this ControlDir."""
4040+ push_result = GitPushResult()
4041+ push_result.workingtree_updated = None
4042+ push_result.master_branch = None
4043+ push_result.source_branch = source
4044+ push_result.stacked_on = None
4045+ repo = self.find_repository()
4046+ refname = self._get_selected_ref(name)
4047+ from .branch import GitBranch
4048+ if isinstance(source, GitBranch) and lossy:
4049+ raise bzr_errors.LossyPushToSameVCS(source.controldir, self)
4050+ target = self.open_branch(name, nascent_ok=True)
4051+ push_result.branch_push_result = source.push(
4052+ target, overwrite=overwrite, stop_revision=revision_id,
4053+ lossy=lossy)
4054+ push_result.new_revid = push_result.branch_push_result.new_revid
4055+ push_result.old_revid = push_result.branch_push_result.old_revid
4056+ push_result.target_branch = self.open_branch(name)
4057+ if source.get_push_location() is None or remember:
4058+ source.set_push_location(push_result.target_branch.base)
4059+ return push_result
4060+
4061+
4062+class LocalGitControlDirFormat(GitControlDirFormat):
4063+ """The .git directory control format."""
4064+
4065+ bare = False
4066+
4067+ @classmethod
4068+ def _known_formats(self):
4069+ return set([LocalGitControlDirFormat()])
4070+
4071+ @property
4072+ def repository_format(self):
4073+ from .repository import GitRepositoryFormat
4074+ return GitRepositoryFormat()
4075+
4076+ @property
4077+ def workingtree_format(self):
4078+ from .workingtree import GitWorkingTreeFormat
4079+ return GitWorkingTreeFormat()
4080+
4081+ def get_branch_format(self):
4082+ from .branch import LocalGitBranchFormat
4083+ return LocalGitBranchFormat()
4084+
4085+ def open(self, transport, _found=None):
4086+ """Open this directory.
4087+
4088+ """
4089+ from .transportgit import TransportRepo
4090+ gitrepo = TransportRepo(transport, self.bare,
4091+ refs_text=getattr(self, "_refs_text", None))
4092+ if not gitrepo._controltransport.has('HEAD'):
4093+ raise bzr_errors.NotBranchError(path=transport.base)
4094+ return LocalGitDir(transport, gitrepo, self)
4095+
4096+ def get_format_description(self):
4097+ return "Local Git Repository"
4098+
4099+ def initialize_on_transport(self, transport):
4100+ from .transportgit import TransportRepo
4101+ repo = TransportRepo.init(transport, bare=self.bare)
4102+ return self.open(transport)
4103+
4104+ def initialize_on_transport_ex(self, transport, use_existing_dir=False,
4105+ create_prefix=False, force_new_repo=False, stacked_on=None,
4106+ stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
4107+ shared_repo=False, vfs_only=False):
4108+ def make_directory(transport):
4109+ transport.mkdir('.')
4110+ return transport
4111+ def redirected(transport, e, redirection_notice):
4112+ trace.note(redirection_notice)
4113+ return transport._redirected_to(e.source, e.target)
4114+ try:
4115+ transport = do_catching_redirections(make_directory, transport,
4116+ redirected)
4117+ except bzr_errors.FileExists:
4118+ if not use_existing_dir:
4119+ raise
4120+ except bzr_errors.NoSuchFile:
4121+ if not create_prefix:
4122+ raise
4123+ transport.create_prefix()
4124+ controldir = self.initialize_on_transport(transport)
4125+ if repo_format_name:
4126+ result_repo = controldir.find_repository()
4127+ repository_policy = UseExistingRepository(result_repo)
4128+ result_repo.lock_write()
4129+ else:
4130+ result_repo = None
4131+ repository_policy = None
4132+ return (result_repo, controldir, False,
4133+ repository_policy)
4134+
4135+ def is_supported(self):
4136+ return True
4137+
4138+ def supports_transport(self, transport):
4139+ try:
4140+ external_url = transport.external_url()
4141+ except bzr_errors.InProcessTransport:
4142+ raise bzr_errors.NotBranchError(path=transport.base)
4143+ return external_url.startswith("file:")
4144+
4145+
4146+class BareLocalGitControlDirFormat(LocalGitControlDirFormat):
4147+
4148+ bare = True
4149+ supports_workingtrees = False
4150+
4151+ def get_format_description(self):
4152+ return "Local Git Repository (bare)"
4153+
4154+
4155+class LocalGitDir(GitDir):
4156+ """An adapter to the '.git' dir used by git."""
4157+
4158+ def _get_gitrepository_class(self):
4159+ from .repository import LocalGitRepository
4160+ return LocalGitRepository
4161+
4162+ def __repr__(self):
4163+ return "<%s at %r>" % (
4164+ self.__class__.__name__, self.root_transport.base)
4165+
4166+ _gitrepository_class = property(_get_gitrepository_class)
4167+
4168+ @property
4169+ def user_transport(self):
4170+ return self.root_transport
4171+
4172+ @property
4173+ def control_transport(self):
4174+ return self._git._controltransport
4175+
4176+ def __init__(self, transport, gitrepo, format):
4177+ self._format = format
4178+ self.root_transport = transport
4179+ self._mode_check_done = False
4180+ self._git = gitrepo
4181+ if gitrepo.bare:
4182+ self.transport = transport
4183+ else:
4184+ self.transport = transport.clone('.git')
4185+ self._mode_check_done = None
4186+
4187+ def is_control_filename(self, filename):
4188+ return (filename == '.git' or
4189+ filename.startswith('.git/') or
4190+ filename.startswith('.git\\'))
4191+
4192+ def _get_symref(self, ref):
4193+ from dulwich.repo import SYMREF
4194+ ref_chain, unused_sha = self._git.refs.follow(ref)
4195+ if len(ref_chain) == 1:
4196+ return None
4197+ return ref_chain[1]
4198+
4199+ def set_branch_reference(self, target_branch, name=None):
4200+ ref = self._get_selected_ref(name)
4201+ if self.control_transport.base == target_branch.controldir.control_transport.base:
4202+ if ref == target_branch.ref:
4203+ raise BranchReferenceLoop(target_branch)
4204+ self._git.refs.set_symbolic_ref(ref, target_branch.ref)
4205+ else:
4206+ try:
4207+ target_path = target_branch.controldir.control_transport.local_abspath('.')
4208+ except bzr_errors.NotLocalUrl:
4209+ raise bzr_errors.IncompatibleFormat(target_branch._format, self._format)
4210+ # TODO(jelmer): Do some consistency checking across branches..
4211+ self.control_transport.put_bytes('commondir', target_path.encode('utf-8'))
4212+ # TODO(jelmer): Urgh, avoid mucking about with internals.
4213+ self._git._commontransport = target_branch.repository._git._commontransport.clone()
4214+ self._git.object_store = TransportObjectStore(self._git._commontransport.clone(OBJECTDIR))
4215+ self._git.refs.transport = self._git._commontransport
4216+ target_ref_chain, unused_sha = target_branch.controldir._git.refs.follow(target_branch.ref)
4217+ for target_ref in target_ref_chain:
4218+ if target_ref == b'HEAD':
4219+ continue
4220+ break
4221+ else:
4222+ # Can't create a reference to something that is not a in a repository.
4223+ raise bzr_errors.IncompatibleFormat(self.set_branch_reference, self)
4224+ self._git.refs.set_symbolic_ref(ref, target_ref)
4225+
4226+ def get_branch_reference(self, name=None):
4227+ ref = self._get_selected_ref(name)
4228+ target_ref = self._get_symref(ref)
4229+ if target_ref is not None:
4230+ from .refs import ref_to_branch_name
4231+ try:
4232+ branch_name = ref_to_branch_name(target_ref)
4233+ except ValueError:
4234+ params = {'ref': urllib.quote(target_ref, '')}
4235+ else:
4236+ if branch_name != b'':
4237+ params = {'branch': urllib.quote(branch_name.encode('utf-8'), '')}
4238+ else:
4239+ params = {}
4240+ try:
4241+ base_url = urlutils.local_path_to_url(self.control_transport.get_bytes('commondir')).rstrip('/.git/')+'/'
4242+ except bzr_errors.NoSuchFile:
4243+ base_url = self.user_url.rstrip('/')
4244+ return urlutils.join_segment_parameters(base_url, params)
4245+ return None
4246+
4247+ def find_branch_format(self, name=None):
4248+ from .branch import (
4249+ LocalGitBranchFormat,
4250+ )
4251+ ref = self._get_selected_ref(name)
4252+ return LocalGitBranchFormat()
4253+
4254+ def get_branch_transport(self, branch_format, name=None):
4255+ if branch_format is None:
4256+ return self.transport
4257+ if isinstance(branch_format, LocalGitControlDirFormat):
4258+ return self.transport
4259+ raise bzr_errors.IncompatibleFormat(branch_format, self._format)
4260+
4261+ def get_repository_transport(self, format):
4262+ if format is None:
4263+ return self.transport
4264+ if isinstance(format, LocalGitControlDirFormat):
4265+ return self.transport
4266+ raise bzr_errors.IncompatibleFormat(format, self._format)
4267+
4268+ def get_workingtree_transport(self, format):
4269+ if format is None:
4270+ return self.transport
4271+ if isinstance(format, LocalGitControlDirFormat):
4272+ return self.transport
4273+ raise bzr_errors.IncompatibleFormat(format, self._format)
4274+
4275+ def open_branch(self, name=None, unsupported=False, ignore_fallbacks=None,
4276+ ref=None, possible_transports=None, nascent_ok=False):
4277+ """'create' a branch for this dir."""
4278+ repo = self.find_repository()
4279+ from .branch import LocalGitBranch
4280+ ref = self._get_selected_ref(name, ref)
4281+ if not nascent_ok and ref not in self._git.refs:
4282+ raise bzr_errors.NotBranchError(self.root_transport.base,
4283+ controldir=self)
4284+ ref_chain, unused_sha = self._git.refs.follow(ref)
4285+ if ref_chain[-1] == b'HEAD':
4286+ controldir = self
4287+ else:
4288+ controldir = self._find_commondir()
4289+ return LocalGitBranch(controldir, repo, ref_chain[-1])
4290+
4291+ def destroy_branch(self, name=None):
4292+ refname = self._get_selected_ref(name)
4293+ if refname == b'HEAD':
4294+ # HEAD can't be removed
4295+ raise bzr_errors.UnsupportedOperation(
4296+ self.destroy_branch, self)
4297+ try:
4298+ del self._git.refs[refname]
4299+ except KeyError:
4300+ raise bzr_errors.NotBranchError(self.root_transport.base,
4301+ controldir=self)
4302+
4303+ def destroy_repository(self):
4304+ raise bzr_errors.UnsupportedOperation(self.destroy_repository, self)
4305+
4306+ def destroy_workingtree(self):
4307+ raise bzr_errors.UnsupportedOperation(self.destroy_workingtree, self)
4308+
4309+ def destroy_workingtree_metadata(self):
4310+ raise bzr_errors.UnsupportedOperation(self.destroy_workingtree_metadata, self)
4311+
4312+ def needs_format_conversion(self, format=None):
4313+ return not isinstance(self._format, format.__class__)
4314+
4315+ def open_repository(self):
4316+ """'open' a repository for this dir."""
4317+ if self.control_transport.has('commondir'):
4318+ raise bzr_errors.NoRepositoryPresent(self)
4319+ return self._gitrepository_class(self)
4320+
4321+ def has_workingtree(self):
4322+ return not self._git.bare
4323+
4324+ def open_workingtree(self, recommend_upgrade=True, unsupported=False):
4325+ if not self._git.bare:
4326+ from dulwich.errors import NoIndexPresent
4327+ repo = self.find_repository()
4328+ from .workingtree import GitWorkingTree
4329+ branch = self.open_branch(ref=b'HEAD', nascent_ok=True)
4330+ return GitWorkingTree(self, repo, branch)
4331+ loc = urlutils.unescape_for_display(self.root_transport.base, 'ascii')
4332+ raise bzr_errors.NoWorkingTree(loc)
4333+
4334+ def create_repository(self, shared=False):
4335+ from .repository import GitRepositoryFormat
4336+ if shared:
4337+ raise bzr_errors.IncompatibleFormat(GitRepositoryFormat(), self._format)
4338+ return self.find_repository()
4339+
4340+ def create_branch(self, name=None, repository=None,
4341+ append_revisions_only=None, ref=None):
4342+ refname = self._get_selected_ref(name, ref)
4343+ if refname != b'HEAD' and refname in self._git.refs:
4344+ raise bzr_errors.AlreadyBranchError(self.user_url)
4345+ repo = self.open_repository()
4346+ if refname in self._git.refs:
4347+ ref_chain, unused_sha = self._git.refs.follow(self._get_selected_ref(None))
4348+ if ref_chain[0] == b'HEAD':
4349+ refname = ref_chain[1]
4350+ from .branch import LocalGitBranch
4351+ branch = LocalGitBranch(self, repo, refname)
4352+ if append_revisions_only:
4353+ branch.set_append_revisions_only(append_revisions_only)
4354+ return branch
4355+
4356+ def backup_bzrdir(self):
4357+ if not self._git.bare:
4358+ self.root_transport.copy_tree(".git", ".git.backup")
4359+ return (self.root_transport.abspath(".git"),
4360+ self.root_transport.abspath(".git.backup"))
4361+ else:
4362+ basename = urlutils.basename(self.root_transport.base)
4363+ parent = self.root_transport.clone('..')
4364+ parent.copy_tree(basename, basename + ".backup")
4365+
4366+ def create_workingtree(self, revision_id=None, from_branch=None,
4367+ accelerator_tree=None, hardlink=False):
4368+ if self._git.bare:
4369+ raise bzr_errors.UnsupportedOperation(self.create_workingtree, self)
4370+ if from_branch is None:
4371+ from_branch = self.open_branch(nascent_ok=True)
4372+ if revision_id is None:
4373+ revision_id = from_branch.last_revision()
4374+ repo = self.find_repository()
4375+ from .workingtree import GitWorkingTree
4376+ wt = GitWorkingTree(self, repo, from_branch)
4377+ wt.set_last_revision(revision_id)
4378+ wt._build_checkout_with_index()
4379+ return wt
4380+
4381+ def _find_or_create_repository(self, force_new_repo=None):
4382+ return self.create_repository(shared=False)
4383+
4384+ def _find_creation_modes(self):
4385+ """Determine the appropriate modes for files and directories.
4386+
4387+ They're always set to be consistent with the base directory,
4388+ assuming that this transport allows setting modes.
4389+ """
4390+ # TODO: Do we need or want an option (maybe a config setting) to turn
4391+ # this off or override it for particular locations? -- mbp 20080512
4392+ if self._mode_check_done:
4393+ return
4394+ self._mode_check_done = True
4395+ try:
4396+ st = self.transport.stat('.')
4397+ except bzr_errors.TransportNotPossible:
4398+ self._dir_mode = None
4399+ self._file_mode = None
4400+ else:
4401+ # Check the directory mode, but also make sure the created
4402+ # directories and files are read-write for this user. This is
4403+ # mostly a workaround for filesystems which lie about being able to
4404+ # write to a directory (cygwin & win32)
4405+ if (st.st_mode & 07777 == 00000):
4406+ # FTP allows stat but does not return dir/file modes
4407+ self._dir_mode = None
4408+ self._file_mode = None
4409+ else:
4410+ self._dir_mode = (st.st_mode & 07777) | 00700
4411+ # Remove the sticky and execute bits for files
4412+ self._file_mode = self._dir_mode & ~07111
4413+
4414+ def _get_file_mode(self):
4415+ """Return Unix mode for newly created files, or None.
4416+ """
4417+ if not self._mode_check_done:
4418+ self._find_creation_modes()
4419+ return self._file_mode
4420+
4421+ def _get_dir_mode(self):
4422+ """Return Unix mode for newly created directories, or None.
4423+ """
4424+ if not self._mode_check_done:
4425+ self._find_creation_modes()
4426+ return self._dir_mode
4427+
4428+ def get_refs_container(self):
4429+ return self._git.refs
4430+
4431+ def get_peeled(self, ref):
4432+ return self._git.get_peeled(ref)
4433+
4434+ def _find_commondir(self):
4435+ try:
4436+ commondir = self.control_transport.get_bytes('commondir')
4437+ except bzr_errors.NoSuchFile:
4438+ return self
4439+ else:
4440+ commondir = commondir.rstrip('/.git/')
4441+ return ControlDir.open_from_transport(get_transport_from_path(commondir))
4442
4443=== added file 'breezy/plugins/git/directory.py'
4444--- breezy/plugins/git/directory.py 1970-01-01 00:00:00 +0000
4445+++ breezy/plugins/git/directory.py 2018-05-10 00:44:29 +0000
4446@@ -0,0 +1,30 @@
4447+# Copyright (C) 2012-2018 Jelmer Vernooij <jelmer@jelmer.uk>
4448+
4449+# This program is free software; you can redistribute it and/or modify
4450+# it under the terms of the GNU General Public License as published by
4451+# the Free Software Foundation; either version 2 of the License, or
4452+# (at your option) any later version.
4453+#
4454+# This program is distributed in the hope that it will be useful,
4455+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4456+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4457+# GNU General Public License for more details.
4458+#
4459+# You should have received a copy of the GNU General Public License
4460+# along with this program; if not, write to the Free Software
4461+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4462+
4463+
4464+"""Directory service for gitorious."""
4465+
4466+from __future__ import absolute_import
4467+
4468+from ... import transport
4469+
4470+transport.register_urlparse_netloc_protocol('github')
4471+
4472+class GitHubDirectory(object):
4473+
4474+ def look_up(self, name, url):
4475+ """See DirectoryService.look_up"""
4476+ return "git+ssh://git@github.com/" + name
4477
4478=== added file 'breezy/plugins/git/errors.py'
4479--- breezy/plugins/git/errors.py 1970-01-01 00:00:00 +0000
4480+++ breezy/plugins/git/errors.py 2018-05-10 00:44:29 +0000
4481@@ -0,0 +1,80 @@
4482+# Copyright (C) 2007 Canonical Ltd
4483+# Copyright (C) 2018 Jelmer Vernooij <jelmer@jelmer.uk>
4484+#
4485+# This program is free software; you can redistribute it and/or modify
4486+# it under the terms of the GNU General Public License as published by
4487+# the Free Software Foundation; either version 2 of the License, or
4488+# (at your option) any later version.
4489+#
4490+# This program is distributed in the hope that it will be useful,
4491+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4492+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4493+# GNU General Public License for more details.
4494+#
4495+# You should have received a copy of the GNU General Public License
4496+# along with this program; if not, write to the Free Software
4497+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4498+
4499+
4500+"""A grouping of Exceptions for bzr-git"""
4501+
4502+from __future__ import absolute_import
4503+
4504+from dulwich import errors as git_errors
4505+
4506+from ... import errors as bzr_errors
4507+
4508+
4509+class BzrGitError(bzr_errors.BzrError):
4510+ """The base-level exception for bzr-git errors."""
4511+
4512+
4513+class NoSuchRef(BzrGitError):
4514+ """Raised when a ref can not be found."""
4515+
4516+ _fmt = "The ref %(ref)s was not found in the repository at %(location)s."
4517+
4518+ def __init__(self, ref, location, present_refs=None):
4519+ self.ref = ref
4520+ self.location = location
4521+ self.present_refs = present_refs
4522+
4523+
4524+def convert_dulwich_error(error):
4525+ """Convert a Dulwich error to a Bazaar error."""
4526+
4527+ if isinstance(error, git_errors.HangupException):
4528+ raise bzr_errors.ConnectionReset(error.msg, "")
4529+ raise error
4530+
4531+
4532+class NoPushSupport(bzr_errors.BzrError):
4533+ _fmt = "Push is not yet supported from %(source)r to %(target)r using %(mapping)r for %(revision_id)r. Try dpush instead."
4534+
4535+ def __init__(self, source, target, mapping, revision_id=None):
4536+ self.source = source
4537+ self.target = target
4538+ self.mapping = mapping
4539+ self.revision_id = revision_id
4540+
4541+
4542+class GitSmartRemoteNotSupported(bzr_errors.UnsupportedOperation):
4543+ _fmt = "This operation is not supported by the Git smart server protocol."
4544+
4545+
4546+class UnknownCommitExtra(bzr_errors.BzrError):
4547+ _fmt = "Unknown extra fields in %(object)r: %(fields)r."
4548+
4549+ def __init__(self, object, fields):
4550+ bzr_errors.BzrError.__init__(self)
4551+ self.object = object
4552+ self.fields = ",".join(fields)
4553+
4554+
4555+class UnknownMercurialCommitExtra(bzr_errors.BzrError):
4556+ _fmt = "Unknown mercurial extra fields in %(object)r: %(fields)r."
4557+
4558+ def __init__(self, object, fields):
4559+ bzr_errors.BzrError.__init__(self)
4560+ self.object = object
4561+ self.fields = ",".join(fields)
4562
4563=== added file 'breezy/plugins/git/fetch.py'
4564--- breezy/plugins/git/fetch.py 1970-01-01 00:00:00 +0000
4565+++ breezy/plugins/git/fetch.py 2018-05-10 00:44:29 +0000
4566@@ -0,0 +1,550 @@
4567+# Copyright (C) 2008-2018 Jelmer Vernooij <jelmer@jelmer.uk>
4568+#
4569+# This program is free software; you can redistribute it and/or modify
4570+# it under the terms of the GNU General Public License as published by
4571+# the Free Software Foundation; either version 2 of the License, or
4572+# (at your option) any later version.
4573+#
4574+# This program is distributed in the hope that it will be useful,
4575+# but WITHOUT ANY WARRANTY; without even the implied warranty of
4576+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4577+# GNU General Public License for more details.
4578+#
4579+# You should have received a copy of the GNU General Public License
4580+# along with this program; if not, write to the Free Software
4581+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4582+
4583+"""Fetching from git into bzr."""
4584+
4585+from __future__ import absolute_import
4586+
4587+from dulwich.objects import (
4588+ Commit,
4589+ Tag,
4590+ Tree,
4591+ S_IFGITLINK,
4592+ S_ISGITLINK,
4593+ ZERO_SHA,
4594+ )
4595+from dulwich.object_store import (
4596+ tree_lookup_path,
4597+ )
4598+from dulwich.walk import Walker
4599+from itertools import (
4600+ imap,
4601+ )
4602+import posixpath
4603+import stat
4604+
4605+from ... import (
4606+ debug,
4607+ errors,
4608+ osutils,
4609+ trace,
4610+ ui,
4611+ )
4612+from ...errors import (
4613+ BzrError,
4614+ )
4615+from ...bzr.inventory import (
4616+ InventoryDirectory,
4617+ InventoryFile,
4618+ InventoryLink,
4619+ TreeReference,
4620+ )
4621+from ...repository import (
4622+ InterRepository,
4623+ )
4624+from ...revision import (
4625+ NULL_REVISION,
4626+ )
4627+from ...bzr.inventorytree import InventoryRevisionTree
4628+from ...testament import (
4629+ StrictTestament3,
4630+ )
4631+from ...tsort import (
4632+ topo_sort,
4633+ )
4634+from ...bzr.versionedfile import (
4635+ ChunkedContentFactory,
4636+ )
4637+
4638+from .mapping import (
4639+ DEFAULT_FILE_MODE,
4640+ mode_is_executable,
4641+ mode_kind,
4642+ warn_unusual_mode,
4643+ )
4644+from .object_store import (
4645+ LRUTreeCache,
4646+ _tree_to_objects,
4647+ )
4648+from .refs import (
4649+ is_tag,
4650+ )
4651+from .remote import (
4652+ RemoteGitRepository,
4653+ )
4654+from .repository import (
4655+ GitRepository,
4656+ GitRepositoryFormat,
4657+ LocalGitRepository,
4658+ )
4659+
4660+
4661+def import_git_blob(texts, mapping, path, name, (base_hexsha, hexsha),
4662+ base_bzr_tree, parent_id, revision_id,
4663+ parent_bzr_trees, lookup_object, (base_mode, mode), store_updater,
4664+ lookup_file_id):
4665+ """Import a git blob object into a bzr repository.
4666+
4667+ :param texts: VersionedFiles to add to
4668+ :param path: Path in the tree
4669+ :param blob: A git blob
4670+ :return: Inventory delta for this file
4671+ """
4672+ if mapping.is_special_file(path):
4673+ return []
4674+ if base_hexsha == hexsha and base_mode == mode:
4675+ # If nothing has changed since the base revision, we're done
4676+ return []
4677+ file_id = lookup_file_id(path)
4678+ if stat.S_ISLNK(mode):
4679+ cls = InventoryLink
4680+ else:
4681+ cls = InventoryFile
4682+ ie = cls(file_id, name.decode("utf-8"), parent_id)
4683+ if ie.kind == "file":
4684+ ie.executable = mode_is_executable(mode)
4685+ if base_hexsha == hexsha and mode_kind(base_mode) == mode_kind(mode):
4686+ base_exec = base_bzr_tree.is_executable(path)
4687+ if ie.kind == "symlink":
4688+ ie.symlink_target = base_bzr_tree.get_symlink_target(path)
4689+ else:
4690+ ie.text_size = base_bzr_tree.get_file_size(path)
4691+ ie.text_sha1 = base_bzr_tree.get_file_sha1(path)
4692+ if ie.kind == "symlink" or ie.executable == base_exec:
4693+ ie.revision = base_bzr_tree.get_file_revision(path)
4694+ else:
4695+ blob = lookup_object(hexsha)
4696+ else:
4697+ blob = lookup_object(hexsha)
4698+ if ie.kind == "symlink":
4699+ ie.revision = None
4700+ ie.symlink_target = blob.data.decode("utf-8")
4701+ else:
4702+ ie.text_size = sum(imap(len, blob.chunked))
4703+ ie.text_sha1 = osutils.sha_strings(blob.chunked)
4704+ # Check what revision we should store
4705+ parent_keys = []
4706+ for ptree in parent_bzr_trees:
4707+ try:
4708+ ppath = ptree.id2path(file_id)
4709+ except errors.NoSuchId:
4710+ continue
4711+ pkind = ptree.kind(ppath, file_id)
4712+ if (pkind == ie.kind and
4713+ ((pkind == "symlink" and ptree.get_symlink_target(ppath, file_id) == ie.symlink_target) or
4714+ (pkind == "file" and ptree.get_file_sha1(ppath, file_id) == ie.text_sha1 and
4715+ ptree.is_executable(ppath, file_id) == ie.executable))):
4716+ # found a revision in one of the parents to use
4717+ ie.revision = ptree.get_file_revision(ppath, file_id)
4718+ break
4719+ parent_key = (file_id, ptree.get_file_revision(ppath, file_id))
4720+ if not parent_key in parent_keys:
4721+ parent_keys.append(parent_key)
4722+ if ie.revision is None:
4723+ # Need to store a new revision
4724+ ie.revision = revision_id
4725+ if ie.revision is None:
4726+ raise ValueError("no file revision set")
4727+ if ie.kind == 'symlink':
4728+ chunks = []
4729+ else:
4730+ chunks = blob.chunked
4731+ texts.insert_record_stream([
4732+ ChunkedContentFactory((file_id, ie.revision),
4733+ tuple(parent_keys), ie.text_sha1, chunks)])
4734+ invdelta = []
4735+ if base_hexsha is not None:
4736+ old_path = path.decode("utf-8") # Renames are not supported yet
4737+ if stat.S_ISDIR(base_mode):
4738+ invdelta.extend(remove_disappeared_children(base_bzr_tree, old_path,
4739+ lookup_object(base_hexsha), [], lookup_object))
4740+ else:
4741+ old_path = None
4742+ new_path = path.decode("utf-8")
4743+ invdelta.append((old_path, new_path, file_id, ie))
4744+ if base_hexsha != hexsha:
4745+ store_updater.add_object(blob, (ie.file_id, ie.revision), path)
4746+ return invdelta
4747+
4748+
4749+class SubmodulesRequireSubtrees(BzrError):
4750+ _fmt = ("The repository you are fetching from contains submodules, "
4751+ "which require a Bazaar format that supports tree references.")
4752+ internal = False
4753+
4754+
4755+def import_git_submodule(texts, mapping, path, name, (base_hexsha, hexsha),
4756+ base_bzr_tree, parent_id, revision_id, parent_bzr_trees, lookup_object,
4757+ (base_mode, mode), store_updater, lookup_file_id):
4758+ """Import a git submodule."""
4759+ if base_hexsha == hexsha and base_mode == mode:
4760+ return [], {}
4761+ file_id = lookup_file_id(path)
4762+ invdelta = []
4763+ ie = TreeReference(file_id, name.decode("utf-8"), parent_id)
4764+ ie.revision = revision_id
4765+ if base_hexsha is not None:
4766+ old_path = path.decode("utf-8") # Renames are not supported yet
4767+ if stat.S_ISDIR(base_mode):
4768+ invdelta.extend(remove_disappeared_children(base_bzr_tree, old_path,
4769+ lookup_object(base_hexsha), [], lookup_object))
4770+ else:
4771+ old_path = None
4772+ ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
4773+ texts.insert_record_stream([
4774+ ChunkedContentFactory((file_id, ie.revision), (), None, [])])
4775+ invdelta.append((old_path, path, file_id, ie))
4776+ return invdelta, {}
4777+
4778+
4779+def remove_disappeared_children(base_bzr_tree, path, base_tree, existing_children,
4780+ lookup_object):
4781+ """Generate an inventory delta for removed children.
4782+
4783+ :param base_bzr_tree: Base bzr tree against which to generate the
4784+ inventory delta.
4785+ :param path: Path to process (unicode)
4786+ :param base_tree: Git Tree base object
4787+ :param existing_children: Children that still exist
4788+ :param lookup_object: Lookup a git object by its SHA1
4789+ :return: Inventory delta, as list
4790+ """
4791+ if type(path) is not unicode:
4792+ raise TypeError(path)
4793+ ret = []
4794+ for name, mode, hexsha in base_tree.iteritems():
4795+ if name in existing_children:
4796+ continue
4797+ c_path = posixpath.join(path, name.decode("utf-8"))
4798+ file_id = base_bzr_tree.path2id(c_path)
4799+ if file_id is None:
4800+ raise TypeError(file_id)
4801+ ret.append((c_path, None, file_id, None))
4802+ if stat.S_ISDIR(mode):
4803+ ret.extend(remove_disappeared_children(
4804+ base_bzr_tree, c_path, lookup_object(hexsha), [], lookup_object))
4805+ return ret
4806+
4807+
4808+def import_git_tree(texts, mapping, path, name, (base_hexsha, hexsha),
4809+ base_bzr_tree, parent_id, revision_id, parent_bzr_trees,
4810+ lookup_object, (base_mode, mode), store_updater,
4811+ lookup_file_id, allow_submodules=False):
4812+ """Import a git tree object into a bzr repository.
4813+
4814+ :param texts: VersionedFiles object to add to
4815+ :param path: Path in the tree (str)
4816+ :param name: Name of the tree (str)
4817+ :param tree: A git tree object
4818+ :param base_bzr_tree: Base inventory against which to return inventory delta
4819+ :return: Inventory delta for this subtree
4820+ """
4821+ if type(path) is not str:
4822+ raise TypeError(path)
4823+ if type(name) is not str:
4824+ raise TypeError(name)
4825+ if base_hexsha == hexsha and base_mode == mode:
4826+ # If nothing has changed since the base revision, we're done
4827+ return [], {}
4828+ invdelta = []
4829+ file_id = lookup_file_id(path)
4830+ # We just have to hope this is indeed utf-8:
4831+ ie = InventoryDirectory(file_id, name.decode("utf-8"), parent_id)
4832+ tree = lookup_object(hexsha)
4833+ if base_hexsha is None:
4834+ base_tree = None
4835+ old_path = None # Newly appeared here
4836+ else:
4837+ base_tree = lookup_object(base_hexsha)
4838+ old_path = path.decode("utf-8") # Renames aren't supported yet
4839+ new_path = path.decode("utf-8")
4840+ if base_tree is None or type(base_tree) is not Tree:
4841+ ie.revision = revision_id
4842+ invdelta.append((old_path, new_path, ie.file_id, ie))
4843+ texts.insert_record_stream([
4844+ ChunkedContentFactory((ie.file_id, ie.revision), (), None, [])])
4845+ # Remember for next time
4846+ existing_children = set()
4847+ child_modes = {}
4848+ for name, child_mode, child_hexsha in tree.iteritems():
4849+ existing_children.add(name)
4850+ child_path = posixpath.join(path, name)
4851+ if type(base_tree) is Tree:
4852+ try:
4853+ child_base_mode, child_base_hexsha = base_tree[name]
4854+ except KeyError:
4855+ child_base_hexsha = None
4856+ child_base_mode = 0
4857+ else:
4858+ child_base_hexsha = None
4859+ child_base_mode = 0
4860+ if stat.S_ISDIR(child_mode):
4861+ subinvdelta, grandchildmodes = import_git_tree(texts, mapping,
4862+ child_path, name, (child_base_hexsha, child_hexsha),
4863+ base_bzr_tree, file_id, revision_id, parent_bzr_trees,
4864+ lookup_object, (child_base_mode, child_mode), store_updater,
4865+ lookup_file_id, allow_submodules=allow_submodules)
4866+ elif S_ISGITLINK(child_mode): # submodule
4867+ if not allow_submodules:
4868+ raise SubmodulesRequireSubtrees()
4869+ subinvdelta, grandchildmodes = import_git_submodule(texts, mapping,
4870+ child_path, name, (child_base_hexsha, child_hexsha),
4871+ base_bzr_tree, file_id, revision_id, parent_bzr_trees,
4872+ lookup_object, (child_base_mode, child_mode), store_updater,
4873+ lookup_file_id)
4874+ else:
4875+ if not mapping.is_special_file(name):
4876+ subinvdelta = import_git_blob(texts, mapping, child_path, name,
4877+ (child_base_hexsha, child_hexsha), base_bzr_tree, file_id,
4878+ revision_id, parent_bzr_trees, lookup_object,
4879+ (child_base_mode, child_mode), store_updater, lookup_file_id)
4880+ else:
4881+ subinvdelta = []
4882+ grandchildmodes = {}
4883+ child_modes.update(grandchildmodes)
4884+ invdelta.extend(subinvdelta)
4885+ if child_mode not in (stat.S_IFDIR, DEFAULT_FILE_MODE,
4886+ stat.S_IFLNK, DEFAULT_FILE_MODE|0111,
4887+ S_IFGITLINK):
4888+ child_modes[child_path] = child_mode
4889+ # Remove any children that have disappeared
4890+ if base_tree is not None and type(base_tree) is Tree:
4891+ invdelta.extend(remove_disappeared_children(base_bzr_tree, old_path,
4892+ base_tree, existing_children, lookup_object))
4893+ store_updater.add_object(tree, (file_id, revision_id), path)
4894+ return invdelta, child_modes
4895+
4896+
4897+def verify_commit_reconstruction(target_git_object_retriever, lookup_object,
4898+ o, rev, ret_tree, parent_trees, mapping, unusual_modes, verifiers):
4899+ new_unusual_modes = mapping.export_unusual_file_modes(rev)
4900+ if new_unusual_modes != unusual_modes:
4901+ raise AssertionError("unusual modes don't match: %r != %r" % (
4902+ unusual_modes, new_unusual_modes))
4903+ # Verify that we can reconstruct the commit properly
4904+ rec_o = target_git_object_retriever._reconstruct_commit(rev, o.tree, True,
4905+ verifiers)
4906+ if rec_o != o:
4907+ raise AssertionError("Reconstructed commit differs: %r != %r" % (
4908+ rec_o, o))
4909+ diff = []
4910+ new_objs = {}
4911+ for path, obj, ie in _tree_to_objects(ret_tree, parent_trees,
4912+ target_git_object_retriever._cache.idmap, unusual_modes,
4913+ mapping.BZR_DUMMY_FILE):
4914+ old_obj_id = tree_lookup_path(lookup_object, o.tree, path)[1]
4915+ new_objs[path] = obj
4916+ if obj.id != old_obj_id:
4917+ diff.append((path, lookup_object(old_obj_id), obj))
4918+ for (path, old_obj, new_obj) in diff:
4919+ while (old_obj.type_name == "tree" and
4920+ new_obj.type_name == "tree" and
4921+ sorted(old_obj) == sorted(new_obj)):
4922+ for name in old_obj:
4923+ if old_obj[name][0] != new_obj[name][0]:
4924+ raise AssertionError("Modes for %s differ: %o != %o" %
4925+ (path, old_obj[name][0], new_obj[name][0]))
4926+ if old_obj[name][1] != new_obj[name][1]:
4927+ # Found a differing child, delve deeper
4928+ path = posixpath.join(path, name)
4929+ old_obj = lookup_object(old_obj[name][1])
4930+ new_obj = new_objs[path]
4931+ break
4932+ raise AssertionError("objects differ for %s: %r != %r" % (path,
4933+ old_obj, new_obj))
4934+
4935+
4936+def ensure_inventories_in_repo(repo, trees):
4937+ real_inv_vf = repo.inventories.without_fallbacks()
4938+ for t in trees:
4939+ revid = t.get_revision_id()
4940+ if not real_inv_vf.get_parent_map([(revid, )]):
4941+ repo.add_inventory(revid, t.inventory, t.get_parent_ids())
4942+
4943+
4944+def import_git_commit(repo, mapping, head, lookup_object,
4945+ target_git_object_retriever, trees_cache):
4946+ o = lookup_object(head)
4947+ # Note that this uses mapping.revision_id_foreign_to_bzr. If the parents
4948+ # were bzr roundtripped revisions they would be specified in the
4949+ # roundtrip data.
4950+ rev, roundtrip_revid, verifiers = mapping.import_commit(
4951+ o, mapping.revision_id_foreign_to_bzr)
4952+ if roundtrip_revid is not None:
4953+ original_revid = rev.revision_id
4954+ rev.revision_id = roundtrip_revid
4955+ # We have to do this here, since we have to walk the tree and
4956+ # we need to make sure to import the blobs / trees with the right
4957+ # path; this may involve adding them more than once.
4958+ parent_trees = trees_cache.revision_trees(rev.parent_ids)
4959+ ensure_inventories_in_repo(repo, parent_trees)
4960+ if parent_trees == []:
4961+ base_bzr_tree = trees_cache.revision_tree(NULL_REVISION)
4962+ base_tree = None
4963+ base_mode = None
4964+ else:
4965+ base_bzr_tree = parent_trees[0]
4966+ base_tree = lookup_object(o.parents[0]).tree
4967+ base_mode = stat.S_IFDIR
4968+ store_updater = target_git_object_retriever._get_updater(rev)
4969+ tree_supplement = mapping.get_fileid_map(lookup_object, o.tree)
4970+ inv_delta, unusual_modes = import_git_tree(repo.texts,
4971+ mapping, "", "", (base_tree, o.tree), base_bzr_tree,
4972+ None, rev.revision_id, parent_trees,
4973+ lookup_object, (base_mode, stat.S_IFDIR), store_updater,
4974+ tree_supplement.lookup_file_id,
4975+ allow_submodules=getattr(repo._format, "supports_tree_reference",
4976+ False))
4977+ if unusual_modes != {}:
4978+ for path, mode in unusual_modes.iteritems():
4979+ warn_unusual_mode(rev.foreign_revid, path, mode)
4980+ mapping.import_unusual_file_modes(rev, unusual_modes)
4981+ try:
4982+ basis_id = rev.parent_ids[0]
4983+ except IndexError:
4984+ basis_id = NULL_REVISION
4985+ base_bzr_inventory = None
4986+ else:
4987+ try:
4988+ base_bzr_inventory = base_bzr_tree.root_inventory
4989+ except AttributeError: # bzr < 2.6
4990+ base_bzr_inventory = base_bzr_tree.inventory
4991+ rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
4992+ inv_delta, rev.revision_id, rev.parent_ids,
4993+ base_bzr_inventory)
4994+ ret_tree = InventoryRevisionTree(repo, inv, rev.revision_id)
4995+ # Check verifiers
4996+ if verifiers and roundtrip_revid is not None:
4997+ testament = StrictTestament3(rev, ret_tree)
4998+ calculated_verifiers = { "testament3-sha1": testament.as_sha1() }
4999+ if calculated_verifiers != verifiers:
5000+ trace.mutter("Testament SHA1 %r for %r did not match %r.",
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches