gnu: breezy: Update to 3.2.2.
* gnu/packages/patches/breezy-fix-gio.patch: New file. * gnu/local.mk (dist_patch_DATA): Register it. * gnu/packages/version-control.scm (breezy): Update to 3.2.2. [source]{snippet}: Delete pre-generated cythonized C files. {patches}: Apply patch. [tests?]: Disable tests. [phases]{patch-test-shebangs, check}: New phase and override. [native-inputs]: Add nano, python-cython, python-docutils, and python-testrepository. [inputs]: Use new style. Add python-launchpadlib and python-pygobject. [description]: Adjust Bazaar URL.
parent
dad15bdb26
commit
dc71b4f1c6
|
@ -916,6 +916,7 @@ dist_patch_DATA = \
|
||||||
%D%/packages/patches/bloomberg-bde-cmake-module-path.patch \
|
%D%/packages/patches/bloomberg-bde-cmake-module-path.patch \
|
||||||
%D%/packages/patches/bloomberg-bde-tools-fix-install-path.patch \
|
%D%/packages/patches/bloomberg-bde-tools-fix-install-path.patch \
|
||||||
%D%/packages/patches/bpftrace-disable-bfd-disasm.patch \
|
%D%/packages/patches/bpftrace-disable-bfd-disasm.patch \
|
||||||
|
%D%/packages/patches/breezy-fix-gio.patch \
|
||||||
%D%/packages/patches/byobu-writable-status.patch \
|
%D%/packages/patches/byobu-writable-status.patch \
|
||||||
%D%/packages/patches/bubblewrap-fix-locale-in-tests.patch \
|
%D%/packages/patches/bubblewrap-fix-locale-in-tests.patch \
|
||||||
%D%/packages/patches/cabal-install-base16-bytestring1.0.patch \
|
%D%/packages/patches/cabal-install-base16-bytestring1.0.patch \
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
This patch combines https://code.launchpad.net/~jelmer/brz/enable-gio/+merge/419150
|
||||||
|
and https://bazaar.launchpad.net/~jelmer/brz/fix-gio/revision/7570.
|
||||||
|
|
||||||
|
=== modified file 'breezy/transport/gio_transport.py'
|
||||||
|
--- a/breezy/transport/gio_transport.py 2022-04-09 12:17:41 +0000
|
||||||
|
+++ b/breezy/transport/gio_transport.py 2022-04-09 12:33:51 +0000
|
||||||
|
@@ -52,11 +52,7 @@
|
||||||
|
from ..tests.test_server import TestServer
|
||||||
|
|
||||||
|
try:
|
||||||
|
- import glib
|
||||||
|
-except ImportError as e:
|
||||||
|
- raise errors.DependencyNotPresent('glib', e)
|
||||||
|
-try:
|
||||||
|
- import gio
|
||||||
|
+ from gi.repository import Gio as gio
|
||||||
|
except ImportError as e:
|
||||||
|
raise errors.DependencyNotPresent('gio', e)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -57,6 +57,9 @@
|
||||||
|
raise errors.DependencyNotPresent('gio', e)
|
||||||
|
|
||||||
|
|
||||||
|
+from gi.repository.GLib import GError
|
||||||
|
+
|
||||||
|
+
|
||||||
|
class GioLocalURLServer(TestServer):
|
||||||
|
"""A pretend server for local transports, using file:// urls.
|
||||||
|
|
||||||
|
@@ -81,7 +84,7 @@
|
||||||
|
def __init__(self, transport, relpath):
|
||||||
|
FileStream.__init__(self, transport, relpath)
|
||||||
|
self.gio_file = transport._get_GIO(relpath)
|
||||||
|
- self.stream = self.gio_file.create()
|
||||||
|
+ self.stream = self.gio_file.create(0, None)
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
self.stream.close()
|
||||||
|
@@ -90,7 +93,7 @@
|
||||||
|
try:
|
||||||
|
# Using pump_string_file seems to make things crash
|
||||||
|
osutils.pumpfile(BytesIO(bytes), self.stream)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
# self.transport._translate_gio_error(e,self.relpath)
|
||||||
|
raise errors.BzrError(str(e))
|
||||||
|
|
||||||
|
@@ -98,12 +101,12 @@
|
||||||
|
class GioStatResult(object):
|
||||||
|
|
||||||
|
def __init__(self, f):
|
||||||
|
- info = f.query_info('standard::size,standard::type')
|
||||||
|
+ info = f.query_info('standard::size,standard::type', 0, None)
|
||||||
|
self.st_size = info.get_size()
|
||||||
|
type = info.get_file_type()
|
||||||
|
- if (type == gio.FILE_TYPE_REGULAR):
|
||||||
|
+ if type == gio.FileType.REGULAR:
|
||||||
|
self.st_mode = stat.S_IFREG
|
||||||
|
- elif type == gio.FILE_TYPE_DIRECTORY:
|
||||||
|
+ elif type == gio.FileType.DIRECTORY:
|
||||||
|
self.st_mode = stat.S_IFDIR
|
||||||
|
|
||||||
|
|
||||||
|
@@ -122,7 +125,7 @@
|
||||||
|
user, netloc = netloc.rsplit('@', 1)
|
||||||
|
# Seems it is not possible to list supported backends for GIO
|
||||||
|
# so a hardcoded list it is then.
|
||||||
|
- gio_backends = ['dav', 'file', 'ftp', 'obex', 'sftp', 'ssh', 'smb']
|
||||||
|
+ gio_backends = ['dav', 'file', 'ftp', 'obex', 'sftp', 'ssh', 'smb', 'http']
|
||||||
|
if scheme not in gio_backends:
|
||||||
|
raise urlutils.InvalidURL(base,
|
||||||
|
extra="GIO support is only available for " +
|
||||||
|
@@ -138,13 +141,10 @@
|
||||||
|
_from_transport=_from_transport)
|
||||||
|
|
||||||
|
def _relpath_to_url(self, relpath):
|
||||||
|
- full_url = urlutils.join(self.url, relpath)
|
||||||
|
- if isinstance(full_url, str):
|
||||||
|
- raise urlutils.InvalidURL(full_url)
|
||||||
|
- return full_url
|
||||||
|
+ return urlutils.join(self.url, relpath)
|
||||||
|
|
||||||
|
def _get_GIO(self, relpath):
|
||||||
|
- """Return the ftplib.GIO instance for this object."""
|
||||||
|
+ """Return the GIO instance for this object."""
|
||||||
|
# Ensures that a connection is established
|
||||||
|
connection = self._get_connection()
|
||||||
|
if connection is None:
|
||||||
|
@@ -152,7 +152,7 @@
|
||||||
|
connection, credentials = self._create_connection()
|
||||||
|
self._set_connection(connection, credentials)
|
||||||
|
fileurl = self._relpath_to_url(relpath)
|
||||||
|
- file = gio.File(fileurl)
|
||||||
|
+ file = gio.File.new_for_uri(fileurl)
|
||||||
|
return file
|
||||||
|
|
||||||
|
def _auth_cb(self, op, message, default_user, default_domain, flags):
|
||||||
|
@@ -197,7 +197,7 @@
|
||||||
|
try:
|
||||||
|
obj.mount_enclosing_volume_finish(res)
|
||||||
|
self.loop.quit()
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self.loop.quit()
|
||||||
|
raise errors.BzrError(
|
||||||
|
"Failed to mount the given location: " + str(e))
|
||||||
|
@@ -209,12 +209,12 @@
|
||||||
|
user, password = credentials
|
||||||
|
|
||||||
|
try:
|
||||||
|
- connection = gio.File(self.url)
|
||||||
|
+ connection = gio.File.new_for_uri(self.url)
|
||||||
|
mount = None
|
||||||
|
try:
|
||||||
|
mount = connection.find_enclosing_mount()
|
||||||
|
- except gio.Error as e:
|
||||||
|
- if (e.code == gio.ERROR_NOT_MOUNTED):
|
||||||
|
+ except GError as e:
|
||||||
|
+ if e.code == gio.IOErrorEnum.NOT_MOUNTED:
|
||||||
|
self.loop = glib.MainLoop()
|
||||||
|
ui.ui_factory.show_message('Mounting %s using GIO' %
|
||||||
|
self.url)
|
||||||
|
@@ -227,7 +227,7 @@
|
||||||
|
m = connection.mount_enclosing_volume(op,
|
||||||
|
self._mount_done_cb)
|
||||||
|
self.loop.run()
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
raise errors.TransportError(msg="Error setting up connection:"
|
||||||
|
" %s" % str(e), orig_error=e)
|
||||||
|
return connection, (user, password)
|
||||||
|
@@ -257,8 +257,8 @@
|
||||||
|
if stat.S_ISREG(st.st_mode) or stat.S_ISDIR(st.st_mode):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
- except gio.Error as e:
|
||||||
|
- if e.code == gio.ERROR_NOT_FOUND:
|
||||||
|
+ except GError as e:
|
||||||
|
+ if e.code == gio.IOErrorEnum.NOT_FOUND:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
@@ -281,10 +281,10 @@
|
||||||
|
buf = fin.read()
|
||||||
|
fin.close()
|
||||||
|
return BytesIO(buf)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
# If we get a not mounted here it might mean
|
||||||
|
# that a bad path has been entered (or that mount failed)
|
||||||
|
- if (e.code == gio.ERROR_NOT_MOUNTED):
|
||||||
|
+ if e.code == gio.IOErrorEnum.NOT_MOUNTED:
|
||||||
|
raise errors.PathError(relpath,
|
||||||
|
extra='Failed to get file, make sure the path is correct. '
|
||||||
|
+ str(e))
|
||||||
|
@@ -307,19 +307,19 @@
|
||||||
|
closed = True
|
||||||
|
try:
|
||||||
|
f = self._get_GIO(tmppath)
|
||||||
|
- fout = f.create()
|
||||||
|
+ fout = f.create(0, None)
|
||||||
|
closed = False
|
||||||
|
length = self._pump(fp, fout)
|
||||||
|
fout.close()
|
||||||
|
closed = True
|
||||||
|
self.stat(tmppath)
|
||||||
|
dest = self._get_GIO(relpath)
|
||||||
|
- f.move(dest, flags=gio.FILE_COPY_OVERWRITE)
|
||||||
|
+ f.move(dest, flags=gio.FileCopyFlags.OVERWRITE)
|
||||||
|
f = None
|
||||||
|
if mode is not None:
|
||||||
|
self._setmode(relpath, mode)
|
||||||
|
return length
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
finally:
|
||||||
|
if not closed and fout is not None:
|
||||||
|
@@ -335,7 +335,7 @@
|
||||||
|
f = self._get_GIO(relpath)
|
||||||
|
f.make_directory()
|
||||||
|
self._setmode(relpath, mode)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
|
||||||
|
def open_write_stream(self, relpath, mode=None):
|
||||||
|
@@ -369,14 +369,11 @@
|
||||||
|
f.delete()
|
||||||
|
else:
|
||||||
|
raise errors.NotADirectory(relpath)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
except errors.NotADirectory as e:
|
||||||
|
# just pass it forward
|
||||||
|
raise e
|
||||||
|
- except Exception as e:
|
||||||
|
- mutter('failed to rmdir %s: %s' % (relpath, e))
|
||||||
|
- raise errors.PathError(relpath)
|
||||||
|
|
||||||
|
def append_file(self, relpath, file, mode=None):
|
||||||
|
"""Append the text in the file-like object into the final
|
||||||
|
@@ -392,7 +389,7 @@
|
||||||
|
result = 0
|
||||||
|
fo = self._get_GIO(tmppath)
|
||||||
|
fi = self._get_GIO(relpath)
|
||||||
|
- fout = fo.create()
|
||||||
|
+ fout = fo.create(0, None)
|
||||||
|
try:
|
||||||
|
info = GioStatResult(fi)
|
||||||
|
result = info.st_size
|
||||||
|
@@ -400,11 +397,11 @@
|
||||||
|
self._pump(fin, fout)
|
||||||
|
fin.close()
|
||||||
|
# This separate except is to catch and ignore the
|
||||||
|
- # gio.ERROR_NOT_FOUND for the already existing file.
|
||||||
|
+ # gio.IOErrorEnum.NOT_FOUND for the already existing file.
|
||||||
|
# It is valid to open a non-existing file for append.
|
||||||
|
# This is caused by the broken gio append_to...
|
||||||
|
- except gio.Error as e:
|
||||||
|
- if e.code != gio.ERROR_NOT_FOUND:
|
||||||
|
+ except GError as e:
|
||||||
|
+ if e.code != gio.IOErrorEnum.NOT_FOUND:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
length = self._pump(file, fout)
|
||||||
|
fout.close()
|
||||||
|
@@ -413,9 +410,11 @@
|
||||||
|
raise errors.BzrError("Failed to append size after "
|
||||||
|
"(%d) is not original (%d) + written (%d) total (%d)" %
|
||||||
|
(info.st_size, result, length, result + length))
|
||||||
|
- fo.move(fi, flags=gio.FILE_COPY_OVERWRITE)
|
||||||
|
+ fo.move(
|
||||||
|
+ fi, flags=gio.FileCopyFlags.OVERWRITE, cancellable=None,
|
||||||
|
+ progress_callback=None)
|
||||||
|
return result
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
|
||||||
|
def _setmode(self, relpath, mode):
|
||||||
|
@@ -429,8 +428,8 @@
|
||||||
|
try:
|
||||||
|
f = self._get_GIO(relpath)
|
||||||
|
f.set_attribute_uint32(gio.FILE_ATTRIBUTE_UNIX_MODE, mode)
|
||||||
|
- except gio.Error as e:
|
||||||
|
- if e.code == gio.ERROR_NOT_SUPPORTED:
|
||||||
|
+ except GError as e:
|
||||||
|
+ if e.code == gio.IOErrorEnum.NOT_SUPPORTED:
|
||||||
|
# Command probably not available on this server
|
||||||
|
mutter("GIO Could not set permissions to %s on %s. %s",
|
||||||
|
oct(mode), self._remote_path(relpath), str(e))
|
||||||
|
@@ -444,8 +443,8 @@
|
||||||
|
mutter("GIO move (rename): %s => %s", rel_from, rel_to)
|
||||||
|
f = self._get_GIO(rel_from)
|
||||||
|
t = self._get_GIO(rel_to)
|
||||||
|
- f.move(t)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ f.move(t, flags=0, cancellable=None, progress_callback=None)
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, rel_from)
|
||||||
|
|
||||||
|
def move(self, rel_from, rel_to):
|
||||||
|
@@ -455,8 +454,8 @@
|
||||||
|
mutter("GIO move: %s => %s", rel_from, rel_to)
|
||||||
|
f = self._get_GIO(rel_from)
|
||||||
|
t = self._get_GIO(rel_to)
|
||||||
|
- f.move(t, flags=gio.FILE_COPY_OVERWRITE)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ f.move(t, flags=gio.FileCopyFlags.OVERWRITE)
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relfrom)
|
||||||
|
|
||||||
|
def delete(self, relpath):
|
||||||
|
@@ -466,7 +465,7 @@
|
||||||
|
mutter("GIO delete: %s", relpath)
|
||||||
|
f = self._get_GIO(relpath)
|
||||||
|
f.delete()
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
|
||||||
|
def external_url(self):
|
||||||
|
@@ -489,11 +488,11 @@
|
||||||
|
try:
|
||||||
|
entries = []
|
||||||
|
f = self._get_GIO(relpath)
|
||||||
|
- children = f.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_NAME)
|
||||||
|
+ children = f.enumerate_children(gio.FILE_ATTRIBUTE_STANDARD_NAME, 0, None)
|
||||||
|
for child in children:
|
||||||
|
entries.append(urlutils.escape(child.get_name()))
|
||||||
|
return entries
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath)
|
||||||
|
|
||||||
|
def iter_files_recursive(self):
|
||||||
|
@@ -519,7 +518,7 @@
|
||||||
|
mutter("GIO stat: %s", relpath)
|
||||||
|
f = self._get_GIO(relpath)
|
||||||
|
return GioStatResult(f)
|
||||||
|
- except gio.Error as e:
|
||||||
|
+ except GError as e:
|
||||||
|
self._translate_gio_error(e, relpath, extra='error w/ stat')
|
||||||
|
|
||||||
|
def lock_read(self, relpath):
|
||||||
|
@@ -556,21 +555,21 @@
|
||||||
|
mutter("GIO Error: %s %s" % (str(err), path))
|
||||||
|
if extra is None:
|
||||||
|
extra = str(err)
|
||||||
|
- if err.code == gio.ERROR_NOT_FOUND:
|
||||||
|
+ if err.code == gio.IOErrorEnum.NOT_FOUND:
|
||||||
|
raise errors.NoSuchFile(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_EXISTS:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.EXISTS:
|
||||||
|
raise errors.FileExists(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_NOT_DIRECTORY:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.NOT_DIRECTORY:
|
||||||
|
raise errors.NotADirectory(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_NOT_EMPTY:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.NOT_EMPTY:
|
||||||
|
raise errors.DirectoryNotEmpty(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_BUSY:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.BUSY:
|
||||||
|
raise errors.ResourceBusy(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_PERMISSION_DENIED:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.PERMISSION_DENIED:
|
||||||
|
raise errors.PermissionDenied(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_HOST_NOT_FOUND:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.HOST_NOT_FOUND:
|
||||||
|
raise errors.PathError(path, extra=extra)
|
||||||
|
- elif err.code == gio.ERROR_IS_DIRECTORY:
|
||||||
|
+ elif err.code == gio.IOErrorEnum.IS_DIRECTORY:
|
||||||
|
raise errors.PathError(path, extra=extra)
|
||||||
|
else:
|
||||||
|
mutter('unable to understand error for path: %s: %s', path, err)
|
||||||
|
|
|
@ -187,29 +187,63 @@ as well as the classic centralized workflow.")
|
||||||
(uri (string-append "https://launchpad.net/brz/"
|
(uri (string-append "https://launchpad.net/brz/"
|
||||||
(version-major+minor version) "/" version
|
(version-major+minor version) "/" version
|
||||||
"/+download/breezy-" version ".tar.gz"))
|
"/+download/breezy-" version ".tar.gz"))
|
||||||
|
(modules '((guix build utils)))
|
||||||
|
;; Delete pre-generated Cython C files.
|
||||||
|
(snippet '(for-each delete-file (find-files "." "\\pyx.c$")))
|
||||||
(sha256
|
(sha256
|
||||||
(base32 "1md4b6ajawf5h50fqizmjj0g833ihc674dh7fn0mvl4d412nwyhq"))))
|
(base32
|
||||||
|
"1md4b6ajawf5h50fqizmjj0g833ihc674dh7fn0mvl4d412nwyhq"))
|
||||||
|
(patches (search-patches "breezy-fix-gio.patch"))))
|
||||||
(build-system python-build-system)
|
(build-system python-build-system)
|
||||||
;; TODO: Maybe regenerate C files with Cython?
|
|
||||||
(inputs
|
|
||||||
`(("gettext" ,gettext-minimal)
|
|
||||||
("python-configobj" ,python-configobj)
|
|
||||||
("python-dulwich" ,python-dulwich)
|
|
||||||
("python-fastbencode" ,python-fastbencode)
|
|
||||||
("python-fastimport" ,python-fastimport)
|
|
||||||
("python-paramiko" ,python-paramiko)
|
|
||||||
("python-patiencediff" ,python-patiencediff)
|
|
||||||
("python-pycryptodome" ,python-pycryptodome)
|
|
||||||
("python-pygpgme" ,python-pygpgme)))
|
|
||||||
(arguments
|
(arguments
|
||||||
`(#:tests? #f)) ; no tests in release tarball
|
(list
|
||||||
|
#:tests? #f ;FIXME: the test suite hangs
|
||||||
|
#:phases
|
||||||
|
#~(modify-phases %standard-phases
|
||||||
|
(add-after 'unpack 'patch-test-shebangs
|
||||||
|
(lambda _
|
||||||
|
(substitute* (append (find-files "breezy/bzr/tests")
|
||||||
|
(find-files "breezy/tests"))
|
||||||
|
(("#!/bin/sh")
|
||||||
|
(format #f "#!~a" (which "sh"))))))
|
||||||
|
(replace 'check
|
||||||
|
(lambda* (#:key tests? #:allow-other-keys)
|
||||||
|
(when tests?
|
||||||
|
;; The test_read_bundle tests fails with "TypeError: a
|
||||||
|
;; bytes-like object is required, not '_ResultTuple'" (see:
|
||||||
|
;; https://bugs.launchpad.net/brz/+bug/1968415/comments/4).
|
||||||
|
(substitute* "breezy/bzr/tests/__init__.py"
|
||||||
|
(("'test_read_bundle'," all)
|
||||||
|
(string-append "# " all)))
|
||||||
|
(setenv "BZR_EDITOR" "nano")
|
||||||
|
(setenv "HOME" "/tmp")
|
||||||
|
(invoke "testr" "init")
|
||||||
|
(invoke "testr" "run")))))))
|
||||||
|
(native-inputs
|
||||||
|
(list nano ;for tests
|
||||||
|
python-cython
|
||||||
|
python-docutils
|
||||||
|
python-subunit
|
||||||
|
python-testrepository))
|
||||||
|
(inputs
|
||||||
|
(list gettext-minimal
|
||||||
|
python-configobj
|
||||||
|
python-dulwich
|
||||||
|
python-fastbencode
|
||||||
|
python-fastimport
|
||||||
|
python-launchpadlib
|
||||||
|
python-paramiko
|
||||||
|
python-patiencediff
|
||||||
|
python-pycryptodome
|
||||||
|
python-pygobject
|
||||||
|
python-pygpgme))
|
||||||
(home-page "https://www.breezy-vcs.org/")
|
(home-page "https://www.breezy-vcs.org/")
|
||||||
(synopsis "Decentralized revision control system")
|
(synopsis "Decentralized revision control system")
|
||||||
(description
|
(description
|
||||||
"Breezy (@command{brz}) is a decentralized revision control system. By
|
"Breezy (@command{brz}) is a decentralized revision control system. By
|
||||||
default, Breezy provides support for both the
|
default, Breezy provides support for both the
|
||||||
@uref{https://www.bazaar-vcs.org, Bazaar} and @uref{https://www.git-scm.com,
|
@uref{https://bazaar.canonical.com/, Bazaar} and @uref{https://www.git-scm.com,
|
||||||
Git} file formats. Breezy is backwards compatible with Bazaar's disk format
|
Git} file formats. Breezy is backwabrds compatible with Bazaar's disk format
|
||||||
and protocols. One of the key differences with Bazaar is that Breezy runs on
|
and protocols. One of the key differences with Bazaar is that Breezy runs on
|
||||||
Python 3.3 and later, rather than on Python 2.")
|
Python 3.3 and later, rather than on Python 2.")
|
||||||
(license license:gpl2+)))
|
(license license:gpl2+)))
|
||||||
|
|
Reference in New Issue