def run(self, args): location = File(args.location) if args.plain: repository_folder = location else: location.mkdirs() repository_folder = location.child(".filer") init_repository(repository_folder) if not args.plain: repository = Repository(repository_folder) working = WorkingCopy(repository, location) working.create() print "Repository created at %r." % repository_folder.path
class Repository(object): def __init__(self, folder, debug=None): if debug is None: debug = global_debug self.debug = debug self.folder = File(folder) # Sanity check to make sure we're not using the parent folder if self.folder.child(".filer").exists: raise Exception("You should specify the .filer directory as the " "repository to work with, not the folder that contains it") self.store_folder = self.folder.child("store") self.store_folder.mkdirs(True) self.store_folder.child("type").write("direct") self.store = DirectStore(self.store_folder.child("direct")) self.numbers = self.folder.child("numbers") self.numbers.mkdirs(True) self.numbersbyrev = self.folder.child("numbersbyrev") self.numbersbyrev.mkdirs(True) def get_revision(self, id): """ Gets the revision with the specified revision id, which can either be a hex string representing the full sha1 hash or the revision's numeric id. An exceptions.NoSuchObject will be thrown if no such revision exists. The return value is a BEC object corresponding to the revision. Note that short revision numbers must still be given as strings. Bad things (exceptions mainly) will happen if ints are passed in instead. """ if self.numbers.child(id).exists: id = self.numbers.child(id).read() return self.store.get(id) def create_revision(self, data): """ Creates a new revision with the specified data, which should be a BEC object (not a string). The new revision's hash will be returned. Entries in changeparents, changechildren, dirparents, and dirchildren will be created for this new revision. (Update: those have been disabled for now, and will probably be replaced with some sort of graph database soon.) """ if not isinstance(data, dict): raise Exception("Invalid revision data value: %r (must be a dict)" % data) hash = self.store.store(data) if self.debug: print "Wrote revision %s" % hash # TODO: Implement a better algorithm for searching for the next number; # perhaps start at 0 and double until an unused number N is hit, then # do a binary search from 0 to N for the lowest unused number number = 1 while self.numbers.child(str(number)).exists: number += 1 # Add an entry into numbers for this number self.numbers.child(str(number)).write(hash) # Add a reverse entry into numbersbyrev self.numbersbyrev.child(hash).write(str(number)) return hash def revision_iterator(self): """ A generator that returns a (number, hash, data_str, data) tuple for all of the revisions in this repository. number will be a string. """ current_number = 1 while self.numbers.child(str(current_number)).exists: # We've still got a revision, so yield it hash = self.numbers.child(str(current_number)).read() print >>sys.stderr, current_number, hash data = self.store.get(hash) yield str(current_number), hash, data current_number += 1 def number_for_rev(self, hash): """ Returns the short number (as a string) of the specified revision hash. """ return self.numbersbyrev.child(hash).read() def rev_for_number(self, number): if self.numbers.child(number).exists: return self.numbers.child(number).read() # Assume it's a hash instead of a number and just return it return number def has_revision(self, hash): """ Returns True if the specified revision is present in this repository. """ return self.store.has(hash) def is_ancestor(self, descendant, ancestor): descendant, ancestor = self.rev_for_number(descendant), self.rev_for_number(ancestor) if descendant == ancestor: return True current = set(self.get_revision(descendant)["parents"]) while current: rev = current.pop() if rev == ancestor: return True current |= set(self.get_revision(rev)["parents"]) return False