13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""A Git repository implementation that uses a Bazaar transport."""
19
from cStringIO import StringIO
19
from __future__ import absolute_import
21
from io import BytesIO
21
27
from dulwich.errors import (
31
from dulwich.file import (
25
35
from dulwich.objects import (
38
47
load_pack_index_file,
40
49
write_pack_index_v2,
42
51
from dulwich.repo import (
50
64
read_packed_refs_with_peeled,
55
from bzrlib.errors import (
70
transport as _mod_transport,
72
from ...errors import (
73
AlreadyControlDirError,
58
81
TransportNotPossible,
84
from ...lock import LogicalLockResult
62
87
class TransportRefsContainer(RefsContainer):
63
88
"""Refs container that reads refs from a transport."""
65
def __init__(self, transport):
90
def __init__(self, transport, worktree_transport=None):
66
91
self.transport = transport
92
if worktree_transport is None:
93
worktree_transport = transport
94
self.worktree_transport = worktree_transport
67
95
self._packed_refs = None
68
96
self._peeled_refs = None
81
109
def subkeys(self, base):
110
"""Refs present in this container under a base.
112
:param base: The base to return refs under.
113
:return: A set of valid refs in this container under the base; the base
114
prefix is stripped from the ref names returned.
84
iter_files = self.transport.clone(base).iter_files_recursive()
85
keys.update(("%s/%s" % (base, refname)).strip("/") for
86
refname in iter_files if check_ref_format("%s/%s" % (base, refname)))
87
except (TransportNotPossible, NoSuchFile):
89
for key in self.get_packed_refs():
90
if key.startswith(base):
91
keys.add(key[len(base):].strip("/"))
117
base_len = len(base) + 1
118
for refname in self.allkeys():
119
if refname.startswith(base):
120
keys.add(refname[base_len:])
94
123
def allkeys(self):
96
if self.transport.has("HEAD"):
126
self.worktree_transport.get_bytes("HEAD")
99
132
iter_files = list(self.transport.clone("refs").iter_files_recursive())
100
133
for filename in iter_files:
101
refname = "refs/%s" % filename
134
refname = "refs/%s" % urllib.unquote(filename)
102
135
if check_ref_format(refname):
103
136
keys.add(refname)
104
137
except (TransportNotPossible, NoSuchFile):
228
270
:return: True if the set was successful, False otherwise.
231
realname, _ = self._follow(name)
273
realnames, _ = self.follow(name)
274
realname = realnames[-1]
275
except (KeyError, IndexError):
234
self._ensure_dir_exists(realname)
235
self.transport.put_bytes(realname, new_ref+"\n")
277
if realname == b'HEAD':
278
transport = self.worktree_transport
280
transport = self.transport
281
self._ensure_dir_exists(realname)
282
transport.put_bytes(realname, new_ref+"\n")
238
285
def add_if_new(self, name, ref):
246
293
:return: True if the add was successful, False otherwise.
249
realname, contents = self._follow(name)
296
realnames, contents = self.follow(name)
250
297
if contents is not None:
299
realname = realnames[-1]
300
except (KeyError, IndexError):
254
302
self._check_refname(realname)
255
self._ensure_dir_exists(realname)
256
self.transport.put_bytes(realname, ref+"\n")
303
if realname == b'HEAD':
304
transport = self.worktree_transport
306
transport = self.transport
307
self._ensure_dir_exists(realname)
308
transport.put_bytes(realname, ref+"\n")
259
311
def remove_if_equals(self, name, old_ref):
270
322
self._check_refname(name)
271
323
# may only be packed
325
transport = self.worktree_transport
327
transport = self.transport
273
self.transport.delete(name)
329
transport.delete(name)
274
330
except NoSuchFile:
276
332
self._remove_packed_ref(name)
335
def get(self, name, default=None):
341
def unlock_ref(self, name):
343
transport = self.worktree_transport
345
transport = self.transport
346
lockname = name + ".lock"
348
self.transport.delete(lockname)
352
def lock_ref(self, name):
354
transport = self.worktree_transport
356
transport = self.transport
357
self._ensure_dir_exists(name)
358
lockname = name + ".lock"
360
local_path = self.transport.local_abspath(name)
362
# This is racy, but what can we do?
363
if self.transport.has(lockname):
364
raise LockContention(name)
365
lock_result = self.transport.put_bytes(lockname, b'Locked by brz-git')
366
return LogicalLockResult(lambda: self.transport.delete(lockname))
369
gf = GitFile(local_path, 'wb')
370
except FileLocked as e:
371
raise LockContention(name, e)
375
self.transport.delete(lockname)
377
raise LockBroken(lockname)
378
# GitFile.abort doesn't care if the lock has already disappeared
380
return LogicalLockResult(unlock)
280
383
class TransportRepo(BaseRepo):
282
def __init__(self, transport):
385
def __init__(self, transport, bare, refs_text=None):
283
386
self.transport = transport
285
if self.transport.has(".git/%s" % OBJECTDIR):
389
with transport.get(CONTROLDIR) as f:
390
path = read_gitfile(f)
391
except (ReadError, NoSuchFile):
393
self._controltransport = self.transport
287
395
self._controltransport = self.transport.clone('.git')
288
elif self.transport.has_any(["info/refs", OBJECTDIR, REFSDIR]):
290
self._controltransport = self.transport
292
raise NotGitRepository(self.transport)
294
raise NotGitRepository(self.transport)
397
self._controltransport = self.transport.clone(path)
398
commondir = self.get_named_file(COMMONDIR)
399
if commondir is not None:
401
commondir = os.path.join(
403
commondir.read().rstrip(b"\r\n").decode(
404
sys.getfilesystemencoding()))
405
self._commontransport = \
406
_mod_transport.get_transport_from_path(commondir)
408
self._commontransport = self._controltransport
295
409
object_store = TransportObjectStore(
296
self._controltransport.clone(OBJECTDIR))
297
super(TransportRepo, self).__init__(object_store,
298
TransportRefsContainer(self._controltransport))
410
self._commontransport.clone(OBJECTDIR))
411
if refs_text is not None:
412
refs_container = InfoRefsContainer(BytesIO(refs_text))
414
head = TransportRefsContainer(self._commontransport).read_loose_ref("HEAD")
418
refs_container._refs["HEAD"] = head
420
refs_container = TransportRefsContainer(
421
self._commontransport, self._controltransport)
422
super(TransportRepo, self).__init__(object_store,
425
def controldir(self):
426
return self._controltransport.local_abspath('.')
429
return self._commontransport.local_abspath('.')
433
return self.transport.local_abspath('.')
435
def _determine_file_mode(self):
436
# Be consistent with bzr
437
if sys.platform == 'win32':
300
441
def get_named_file(self, path):
301
442
"""Get a file from the control dir with a specific name.
329
473
# missing index file, which is treated as empty.
330
474
return not self.bare
476
def get_config(self):
477
from dulwich.config import ConfigFile
479
return ConfigFile.from_file(self._controltransport.get('config'))
483
def get_config_stack(self):
484
from dulwich.config import StackedConfig
486
p = self.get_config()
492
backends.extend(StackedConfig.default_backends())
493
return StackedConfig(backends, writable=writable)
332
495
def __repr__(self):
333
496
return "<%s for %r>" % (self.__class__.__name__, self.transport)
499
def init(cls, transport, bare=False):
502
transport.mkdir(".git")
504
raise AlreadyControlDirError(transport.base)
505
control_transport = transport.clone(".git")
507
control_transport = transport
508
for d in BASE_DIRECTORIES:
510
control_transport.mkdir("/".join(d))
514
control_transport.mkdir(OBJECTDIR)
516
raise AlreadyControlDirError(transport.base)
517
TransportObjectStore.init(control_transport.clone(OBJECTDIR))
518
ret = cls(transport, bare)
519
ret.refs.set_symbolic_ref(b"HEAD", b"refs/heads/master")
520
ret._init_files(bare)
336
524
class TransportObjectStore(PackBasedObjectStore):
337
525
"""Git-style object store that exists on disk."""
344
532
super(TransportObjectStore, self).__init__()
345
533
self.transport = transport
346
534
self.pack_transport = self.transport.clone(PACKDIR)
535
self._alternates = None
537
def __eq__(self, other):
538
if not isinstance(other, TransportObjectStore):
540
return self.transport == other.transport
348
542
def __repr__(self):
349
543
return "%s(%r)" % (self.__class__.__name__, self.transport)
351
def _pack_cache_stale(self):
546
def alternates(self):
547
if self._alternates is not None:
548
return self._alternates
549
self._alternates = []
550
for path in self._read_alternate_paths():
552
t = _mod_transport.get_transport_from_path(path)
553
self._alternates.append(self.__class__(t))
554
return self._alternates
556
def _read_alternate_paths(self):
558
f = self.transport.get("info/alternates")
563
for l in f.read().splitlines():
575
# FIXME: Never invalidates.
576
if not self._pack_cache:
577
self._update_pack_cache()
578
return self._pack_cache.values()
580
def _update_pack_cache(self):
581
for pack in self._load_packs():
582
self._pack_cache[pack._basename] = pack
354
584
def _pack_names(self):
378
611
# FIXME: This reads the whole pack file at once
379
612
f = self.pack_transport.get(name)
380
613
contents = f.read()
381
pd = PackData(name, StringIO(contents), size=len(contents))
614
pd = PackData(name, BytesIO(contents), size=len(contents))
383
616
pd = PackData(name, self.pack_transport.get(name),
385
618
idxname = name.replace(".pack", ".idx")
386
619
idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
387
620
pack = Pack.from_objects(pd, idx)
621
pack._basename = idxname[:-4]
446
681
idxfile = self.pack_transport.get(basename + ".idx")
447
682
idx = load_pack_index_file(basename+".idx", idxfile)
448
683
final_pack = Pack.from_objects(p, idx)
449
self._add_known_pack(final_pack)
684
final_pack._basename = basename
685
self._add_known_pack(basename, final_pack)
450
686
return final_pack
452
def add_thin_pack(self):
453
"""Add a new thin pack to this object store.
455
Thin packs are packs that contain deltas with parents that exist
458
from cStringIO import StringIO
461
if len(f.getvalue()) > 0:
462
return self.move_in_thin_pack(f)
467
688
def move_in_thin_pack(self, f):
468
689
"""Move a specific file containing a pack into the pack directory.
473
694
:param path: Path to the pack file.
476
data = ThinPackData.from_file(self.get_raw, f, len(f.getvalue()))
477
idx = MemoryPackIndex(data.sorted_entries(), data.get_stored_checksum())
478
p = Pack.from_objects(data, idx)
480
pack_sha = idx.objects_sha1()
482
datafile = self.pack_transport.open_write_stream("pack-%s.pack" % pack_sha)
697
p = Pack('', resolve_ext_ref=self.get_raw)
698
p._data = PackData.from_file(f, len(f.getvalue()))
700
p._idx_load = lambda: MemoryPackIndex(p.data.sorted_entries(), p.data.get_stored_checksum())
702
pack_sha = p.index.objects_sha1()
704
datafile = self.pack_transport.open_write_stream(
705
"pack-%s.pack" % pack_sha)
484
entries, data_sum = write_pack_data(datafile, ((o, None) for o in p.iterobjects()), len(p))
707
entries, data_sum = write_pack_objects(datafile, p.pack_tuples())
488
idxfile = self.pack_transport.open_write_stream("pack-%s.idx" % pack_sha)
710
entries = sorted([(k, v[0], v[1]) for (k, v) in entries.items()])
711
idxfile = self.pack_transport.open_write_stream(
712
"pack-%s.idx" % pack_sha)
490
write_pack_index_v2(idxfile, data.sorted_entries(), data_sum)
714
write_pack_index_v2(idxfile, entries, data_sum)
493
final_pack = Pack("pack-%s" % pack_sha)
494
self._add_known_pack(final_pack)
717
# TODO(jelmer): Just add new pack to the cache
718
self._flush_pack_cache()
499
720
def add_pack(self):
500
"""Add a new pack to this object store.
721
"""Add a new pack to this object store.
502
:return: Fileobject to write to and a commit function to
723
:return: Fileobject to write to and a commit function to
503
724
call when the pack is finished.
505
from cStringIO import StringIO
508
728
if len(f.getvalue()) > 0:
509
729
return self.move_in_pack(f)
734
return f, commit, abort
515
737
def init(cls, transport):
516
transport.mkdir('info')
517
transport.mkdir(PACKDIR)
739
transport.mkdir('info')
743
transport.mkdir(PACKDIR)
518
746
return cls(transport)