reorganized component and repository code into separate modules
authorTimo Savola <tsavola@movial.fi>
Sun, 6 Apr 2008 09:50:25 +0000 (12:50 +0300)
committerTimo Savola <tsavola@movial.fi>
Sun, 6 Apr 2008 09:50:25 +0000 (12:50 +0300)
matrix/build.py
matrix/cache.py
matrix/components.py [new file with mode: 0644]
matrix/config.py
matrix/git.py
matrix/matrix.py
matrix/repositories.py [new file with mode: 0644]

index b99af29..bde1197 100644 (file)
@@ -40,7 +40,7 @@ class Builder(object):
                                        log.error('kill: %s' % e)
 
                                c = self.jobs[pid]
-                               c.active_state = 'killed'
+                               c.state = 'killed'
 
                        while self.jobs:
                                self.wait()
@@ -96,14 +96,14 @@ class Builder(object):
                print progress, message
 
        def is_buildable(self, c):
-               if c.active_state:
+               if c.state:
                        if config.debug:
                                self.message('Component %s is in state: %s' % \
-                                       (c.name, c.active_state))
+                                       (c.name, c.state))
                        return False
 
-               for dep in c.active_depends:
-                       if config.components[dep.name].active_state != 'done':
+               for dep in c.depends:
+                       if config.components[dep.name].state != 'done':
                                if config.debug:
                                        self.message('Component %s depends ' \
                                                'on uninstalled %s' % \
@@ -116,7 +116,7 @@ class Builder(object):
                if not cache.contains(c):
                        return False
 
-               c.active_state = 'done'
+               c.state = 'done'
                self.progress(2)
 
                if config.debug:
@@ -145,7 +145,7 @@ class Builder(object):
        def start_job(self, c, make_target, state):
                pid = spawn_make(c, self.max_make_jobs, make_target)
                self.jobs[pid] = c
-               c.active_state = state
+               c.state = state
 
        def wait(self):
                pid, status = os.wait()
@@ -156,7 +156,7 @@ class Builder(object):
                c = self.jobs[pid]
                del self.jobs[pid]
 
-               if c.active_state == 'in-build':
+               if c.state == 'in-build':
                        if status:
                                log.error('Failed to build %s' % c.name)
                                return
@@ -165,10 +165,10 @@ class Builder(object):
                        if config.debug:
                                self.message('Built', c.repo.path)
 
-                       c.active_state = 'wait-install'
+                       c.state = 'wait-install'
                        self.wait_install.append(c)
 
-               elif c.active_state == 'in-install':
+               elif c.state == 'in-install':
                        assert c == self.in_install
 
                        if status:
@@ -178,12 +178,12 @@ class Builder(object):
                        self.progress()
                        self.message('Finished', c.repo.path)
 
-                       c.active_state = 'done'
+                       c.state = 'done'
                        self.in_install = None
                        cache.update(c)
 
-               elif c.active_state != 'killed':
-                       raise Error('Unexpected state: %s' % c.active_state)
+               elif c.state != 'killed':
+                       raise Error('Unexpected state: %s' % c.state)
 
 def spawn_make(c, jobs, target):
        board = config.boards[config.board]
index 4d11299..df1052d 100644 (file)
@@ -39,7 +39,7 @@ def contains(c):
 
        hash, flagstring = match.groups()
 
-       if hash != get_component_hash(c):
+       if hash != get_hash(c):
                if config.debug:
                        print 'New changes in', c.repo.path
                return False
@@ -66,7 +66,7 @@ def update(c):
        file = open(path, 'w')
        done = False
        try:
-               print >>file, get_component_hash(c), ','.join(c.flags)
+               print >>file, get_hash(c), ','.join(c.flags)
                done = True
        finally:
                file.close()
@@ -81,11 +81,5 @@ def remove(c):
 def get_path(c):
        return os.path.join(config.cache_dir, c.name)
 
-def get_component_hash(c):
-       return '%s+%s' % (get_repository_hash(c.repo),
-                         get_repository_hash(c.meta))
-
-def get_repository_hash(repo):
-       if repo.active_hash is None:
-               repo.active_hash = git.rev_parse(repo.path, 'HEAD')
-       return repo.active_hash
+def get_hash(c):
+       return '%s+%s' % (c.repo.get_hash(), c.meta.get_hash())
diff --git a/matrix/components.py b/matrix/components.py
new file mode 100644 (file)
index 0000000..fcd3370
--- /dev/null
@@ -0,0 +1,153 @@
+# Copyright (C) 2006-2008 Movial Oy
+# Authors: Timo Savola <tsavola@movial.fi>
+
+import os
+import re
+from glob import glob
+from sets import Set as set
+
+import config
+import log
+from repositories import Repository
+
+Error = RuntimeError
+
+class Component(object):
+       from_platform = False
+
+       state = None
+
+       def __init__(self, name, tag='master', tags={}, flags=[]):
+               commits = {}
+               commits.update(tags)
+               commits[None] = tag
+
+               self.name = name
+
+               self.repo = Repository(
+                       'repo/%s' % name,
+                       os.path.join(config.top_dir, 'src', name),
+                       commits,
+                       exclude=['meta'])
+
+               self.meta = Repository(
+                       'meta/%s' % name,
+                       os.path.join(config.top_dir, 'src', name, 'meta'),
+                       commits)
+
+               self.flags = flags
+
+               self.packages = {}
+               self.depends = set()
+
+class PlatformProvidedComponent(Component):
+       """A Component that is provided by the platform.
+          The sources will not be built during install."""
+
+       from_platform = True
+
+def init():
+       targets = []
+       packages = {}
+
+       for c in config.components.itervalues():
+               c.repo.init()
+               c.meta.init()
+
+               if not c.meta.exists():
+                       c.meta.clone()
+
+               init_packages(c, targets, packages)
+
+       init_depends(packages)
+
+       return targets
+
+class Package(object):
+       def __init__(self, name, component):
+               self.name = name
+               self.component = component
+
+               self.depends = ''
+               self.conflicts = ''
+               self.architectures = []
+
+def init_packages(c, targets, packages):
+       for path in glob(os.path.join(c.meta.path, '*.package')):
+               name = os.path.basename(path)[:-8]
+
+               pkg = parse_package(name, c, path)
+               if not pkg:
+                       continue
+
+               c.packages[name] = pkg
+               packages[name] = pkg
+
+               if config.debug:
+                       print 'Component', c.name, 'provides', name
+
+       if c.packages:
+               targets.append(c.name)
+       elif config.debug:
+               print 'Component', c.name, 'does not provide any packages'
+
+def parse_package(name, component, path):
+       pkg = Package(name, component)
+       execfile(path, pkg.__dict__, pkg.__dict__)
+
+       if pkg.architectures:
+               arch = config.boards[config.board].arch
+               if arch not in pkg.architectures:
+                       return None
+
+       return pkg
+
+class Dependency(object):
+       regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
+
+       def __init__(self, spec):
+               match = self.regex.match(spec)
+               if not match:
+                       raise Error('Bad dependency specification: ' + spec)
+
+               self.build, self.name, self.tag_op, self.tag, flags \
+                       = match.groups()
+               self.flags = flags.split()
+
+       def check(self, packages):
+               # TODO: check version and flags
+               return self.name in packages
+
+def init_depends(packages):
+       for pkg in packages.itervalues():
+               for spec in pkg.depends.split():
+                       depname = Dependency(spec).name
+                       deppkg = packages.get(depname)
+
+                       if not deppkg:
+                               log.error('Package %s depends on ' \
+                                         'non-existent package %s' % \
+                                         (pkg.name, depname))
+                               continue
+
+                       if deppkg.component == pkg.component:
+                               continue
+
+                       pkg.component.depends.add(deppkg.component)
+
+       fail = False
+       for pkg in packages.itervalues():
+               for spec in pkg.depends.split():
+                       if not Dependency(spec).check(packages):
+                               fail = True
+                               log.error('Dependency %s failed for %s' % \
+                                         (spec, pkg.name))
+
+               for spec in pkg.conflicts.split():
+                       if Dependency(spec).check(packages):
+                               fail = True
+                               log.error('Package %s conflicts with %s' % \
+                                         (pkg.name, spec))
+
+       if fail:
+               raise Error('Invalid component tree')
index ba8cd1f..b146ba2 100644 (file)
@@ -3,6 +3,7 @@
 #          Kalle Vahlman <kalle.vahlman@movial.fi>
 
 import os as _os
+import components as _components
 
 top_dir = _os.environ.get('MATRIX_TOPDIR', '')
 cache_dir = _os.path.join(top_dir, 'build-cache')
@@ -33,46 +34,12 @@ class Board(object):
 
                boards[name] = self
 
-class _Repository(object):
-       def __init__(self, component, name, path):
-               self.component = component
-               self.name = name
-               self.path = path
-               self.active_url = None
-               self.active_hash = None
-
-       def get_commit(self):
-               commits = self.component.commits
-               return commits.get(board) or commits.get(None)
-
-class Component(object):
-       from_platform = False
-
-       def __init__(self, name, tag='master', tags={}, flags=[]):
-               self.name = name
-
-               self.repo = _Repository(self, 'repo/%s' % name,
-                       _os.path.join(top_dir, 'src', name))
-               self.meta = _Repository(self, 'meta/%s' % name,
-                       _os.path.join(top_dir, 'src', name, 'meta'))
-
-               self.commits = {}
-               self.commits.update(tags)
-               self.commits[None] = tag
-
-               self.flags = flags
-
-               self.active_packages = None
-               self.active_depends = None
-               self.active_state = None
-
-               components[name] = self
-
-       def __cmp__(self, other):
-               return cmp(self.name, other.name)
-
-class PlatformProvidedComponent(Component):
-       """A Component that is provided by the platform.
-          The sources will not be built during install."""
+def Component(*args, **kwargs):
+       c = _components.Component(*args, **kwargs)
+       components[c.name] = c
+       return c
 
-       from_platform = True
+def PlatformProvidedComponent(*args, **kwargs):
+       c = _components.PlatformProvidedComponent(*args, **kwargs)
+       components[c.name] = c
+       return c
index a469ff3..73411eb 100644 (file)
@@ -243,12 +243,21 @@ def cat_file(name, hash, blob=False, size=False):
 def log(name, options, fd=None):
        return call_output(['log'] + options + ['--'], workdir=name, fd=fd)
 
-def exclude(name, line):
-       file = open(os.path.join(name, '.git', 'info', 'exclude'), 'a')
+def exclude(name, lines):
+       path = os.path.join(name, '.git', 'info', 'exclude')
+
+       if config.debug:
+               print 'Adding rules to', path
+
+       file = open(path, 'a')
        try:
-               print >>file, line
+               for line in lines:
+                       print >>file, line
        finally:
                file.close()
 
+def database_path(name):
+       return os.path.join(name, '.git')
+
 def contains_database(name):
-       return os.path.exists(os.path.join(name, '.git'))
+       return os.path.exists(database_path(name))
index 347f499..0534127 100644 (file)
@@ -4,14 +4,12 @@
 #          Kalle Vahlman <kalle.vahlman@movial.fi>
 #          Tuomas Kulve <tuomas.kulve@movial.fi>
 
-import glob
 import os
-import re
 import sys
-import tarfile
 from sets import Set as set
 
 import cache
+import components
 import config
 import git
 import log
@@ -35,7 +33,7 @@ def main():
        if not os.path.exists(config.cache_dir):
                os.makedirs(config.cache_dir)
 
-       all_targets = update_components()
+       all_targets = components.init()
 
        for name in targets:
                if name not in config.components:
@@ -45,17 +43,17 @@ def main():
                targets = all_targets
 
        if command == 'install':
-               build_components(targets, **params)
+               build(targets, **params)
        elif command == 'clone':
-               clone_components(targets)
+               clone(targets)
        elif command == 'clean':
-               clean_components(targets)
+               clean(targets)
        elif command == 'rebase':
-               rebase_components(targets)
+               rebase(targets)
        elif command == 'pull':
-               pull_components(targets)
+               pull(targets)
        elif command == 'source-dist':
-               source_dist_components(targets)
+               source_dist(targets)
        elif command == 'rootfs':
                build_rootfs(**params)
        else:
@@ -170,100 +168,6 @@ def parse_config(path):
                print 'Reading', path
                execfile(path, config.__dict__, config.__dict__)
 
-def update_components():
-       targets = []
-       packages = {}
-
-       for c in config.components.itervalues():
-               update_component_url(c)
-               clone_metadata(c)
-               update_component_packages(c, targets, packages)
-               c.active_depends = []
-
-       update_component_depends(packages)
-
-       return targets
-
-def update_component_url(c):
-       update_repository_url(c.repo)
-       update_repository_url(c.meta)
-
-def update_repository_url(repo):
-       if git.contains_database(repo.path):
-               url = git.getvar(repo.path, 'remote.origin.url')
-               if url:
-                       repo.active_url = url
-                       if config.debug:
-                               print 'Using', repo.active_url
-                       return
-
-       for root in config.roots:
-               for suffix in ('.git', ''):
-                       url = '%s/%s%s' % (root, repo.name, suffix)
-
-                       if config.debug:
-                               print 'Trying', url
-
-                       if not git.peek_remote(url, quiet=True):
-                               continue
-
-                       repo.active_url = url
-                       if config.debug:
-                               print 'Found', repo.active_url
-                       return
-
-       raise Error('Failed to locate repository: ' + repo.name)
-
-def update_component_depends(packages):
-       for pkg in packages.itervalues():
-               for spec in pkg.depends.split():
-                       depname = Depend(spec).name
-                       deppkg = packages.get(depname)
-
-                       if not deppkg:
-                               log.error('Package %s depends on ' \
-                                         'non-existent package %s' % \
-                                         (pkg.name, depname))
-                               continue
-
-                       if deppkg.component == pkg.component:
-                               continue
-
-                       pkg.component.active_depends.append(deppkg.component)
-
-       fail = False
-       for pkg in packages.itervalues():
-               for spec in pkg.depends.split():
-                       if not Depend(spec).check(packages):
-                               fail = True
-                               log.error('Dependency %s failed for %s' % \
-                                         (spec, pkg.name))
-
-               for spec in pkg.conflicts.split():
-                       if Depend(spec).check(packages):
-                               fail = True
-                               log.error('Package %s conflicts with %s' % \
-                                         (pkg.name, spec))
-
-       if fail:
-               raise Error('Invalid component tree')
-
-class Depend(object):
-       regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
-
-       def __init__(self, spec):
-               match = self.regex.match(spec)
-               if not match:
-                       raise Error('Bad dependency specification: ' + spec)
-
-               self.build, self.name, self.tag_op, self.tag, flags \
-                       = match.groups()
-               self.flags = flags.split()
-
-       def check(self, packages):
-               # TODO: check version and flags
-               return self.name in packages
-
 def execute(args):
        if config.debug:
                print 'Executing:', ' '.join(args)
@@ -274,17 +178,14 @@ def execute(args):
 def remove_tree(path):
        execute(['rm', '-rf', path])
 
-def clone_components(targets):
-       if not targets:
-               targets = config.components.keys()
-
+def clone(targets):
        for name in targets:
                c = config.components[name]
                clone_component(c)
 
 def clone_component(c, overwrite=False):
-       have_repo = git.contains_database(c.repo.path)
-       have_meta = git.contains_database(c.meta.path)
+       have_repo = c.repo.exists()
+       have_meta = c.meta.exists()
 
        if not overwrite and have_repo and have_meta:
                return
@@ -297,77 +198,11 @@ def clone_component(c, overwrite=False):
                have_meta = False
 
        if not have_repo:
-               clone_repository(c.repo)
-               git.exclude(c.repo.path, 'meta')
-
+               c.repo.clone()
        if not have_meta:
-               clone_repository(c.meta)
-
-def clone_metadata(c):
-       if not git.contains_database(c.meta.path):
-               clone_repository(c.meta)
-
-def clone_repository(repo):
-       print 'Cloning', repo.path
-
-       if os.path.exists(repo.path):
-               tmp = os.path.join(repo.path, 'tmp')
-               git.clone(tmp, repo.active_url, checkout=False)
-               try:
-                       tmpdb = os.path.join(tmp, '.git')
-                       repodb = os.path.join(repo.path, '.git')
-                       if config.debug:
-                               print 'Renaming "%s" as "%s"' % (tmpdb, repodb)
-                       os.rename(tmpdb, repodb)
-               finally:
-                       os.rmdir(tmp)
-       else:
-               git.clone(repo.path, repo.active_url, checkout=False)
-
-       git.reset(repo.path, repo.get_commit(), hard=True)
-
-def update_component_packages(c, targets, packages):
-       c.active_packages = {}
-
-       for path in glob.glob(os.path.join(c.meta.path, '*.package')):
-               name = os.path.basename(path)[:-8]
-
-               pkg = parse_package(name, c, path)
-               if not pkg:
-                       continue
+               c.meta.clone()
 
-               c.active_packages[name] = pkg
-               packages[name] = pkg
-
-               if config.debug:
-                       print 'Component', c.name, 'provides', name
-
-       if c.active_packages:
-               targets.append(c.name)
-       elif config.debug:
-               print 'Component', c.name, 'does not provide any packages'
-
-class Package(object):
-       def __init__(self, name, component):
-               self.name = name
-               self.component = component
-
-               self.depends = []
-               self.conflicts = []
-               self.architectures = None
-
-def parse_package(name, component, path):
-       pkg = Package(name, component)
-       execfile(path, pkg.__dict__, pkg.__dict__)
-
-       if pkg.architectures:
-               arch = config.boards[config.board].arch
-               if arch not in pkg.architectures:
-                       return None
-
-       return pkg
-
-def build_components(targets, build_jobs=1, make_jobs=1):
+def build(targets, build_jobs=1, make_jobs=1):
        selected = set([config.components[i] for i in targets])
 
        depends = None
@@ -375,122 +210,74 @@ def build_components(targets, build_jobs=1, make_jobs=1):
                depends = set()
 
                for c in selected:
-                       for dep in c.active_depends:
+                       for dep in c.depends:
                                if dep not in selected:
                                        depends.add(dep)
 
                selected |= depends
 
-       components = list(selected)
-       components.sort()
-
        if config.debug:
                print 'Building components:'
-               for c in components:
+
+               sorted = list(selected)
+               sorted.sort(key=lambda c: c.name)
+
+               for c in sorted:
                        print '\t' + c.name
 
-       builder = Builder(components, build_jobs, make_jobs)
+       builder = Builder(selected, build_jobs, make_jobs)
        builder.run()
 
-def clean_components(targets):
-       if not targets:
-               targets = config.components.keys()
-
+def clean(targets):
        for name in targets:
                c = config.components[name]
-               print 'Cleaning', c.repo.path
-
+               c.repo.clean()
                cache.remove(c)
 
-               files = git.ls_files(c.repo.path, ['-o'])
-               paths = [os.path.join(c.repo.path, i) for i in files]
-               paths.sort()
-               paths.reverse()
-
-               for path in paths:
-                       if git.contains_database(path):
-                               continue
-
-                       if config.debug:
-                               print 'Removing', path
-
-                       if os.path.islink(path) or not os.path.isdir(path):
-                               os.remove(path)
-                       else:
-                               remove_tree(path)
-
                for repo in (c.repo, c.meta):
-                       files = git.ls_files(repo.path, ['-m', '-d'])
-                       if files:
+                       if repo.dirty_files():
                                log.error('Dirty files left in %s' % repo.path)
 
 def for_each_repository(func, targets=None):
-       if not targets:
-               targets = config.components.keys()
-
        for name in targets:
                c = config.components[name]
-               if git.contains_database(c.repo.path):
+               if c.repo.exists():
                        func(c.repo)
                func(c.meta)
 
-def rebase_components(targets):
-       for_each_repository(rebase_repository, targets)
-
-def pull_components(targets):
-       for_each_repository(pull_repository, targets)
+def rebase(targets):
+       for_each_repository(lambda repo: repo.rebase(), targets)
 
-def rebase_repository(repo):
-       print 'Rebasing', repo.path
-       git.remote_update(repo.path)
-       git.rebase(repo.path)
+def pull(targets):
+       for_each_repository(lambda repo: repo.pull(), targets)
 
-def pull_repository(repo):
-       print 'Pulling', repo.path
-       git.pull(repo.path)
-
-def source_dist_components(targets):
-       if not targets:
-               targets = config.components.keys()
+def source_dist(targets):
+       location = 'dist'
+       if not os.path.exists(location):
+               os.makedirs(location)
 
        for name in targets:
                c = config.components[name]
-               generate_component_changes(c, 'dist')
-               package_component_sources(c, 'dist')
-
-def generate_component_changes(c, location):
-       print 'Generating change log for', c.repo.path
+               dist_changes(c, location)
+               dist_sources(c, location)
 
+def dist_changes(c, location):
        path = os.path.join(location, c.name) + '.changes'
+       c.repo.dump_log(path)
 
-       pathdir = os.path.dirname(path)
-       if not os.path.exists(pathdir):
-               os.makedirs(pathdir)
-
-       fd = os.open(path, os.O_WRONLY | os.O_CREAT, 0644)
-       git.log(c.repo.path, [c.repo.get_commit()], fd=fd)
-       os.close(fd)
-
-def package_component_sources(c, location):
-       print 'Archiving', c.repo.path
-
-       rev = git.describe(c.repo.path)
+def dist_sources(c, location):
+       rev = c.repo.describe()
        if rev:
                rev = '_' + rev
        else:
                rev = ''
 
-       path = os.path.join(location, c.name) + rev + '.tar.bz2'
+       name = c.name + rev
+       path = os.path.join(location, name) + '.tar.bz2'
        if os.path.exists(path):
                os.remove(path)
 
-       pathdir = os.path.dirname(path)
-       if not os.path.exists(pathdir):
-               os.makedirs(pathdir)
-
-       git.archive(c.repo.path, path,
-                   prefix=os.path.basename(c.name) + '/',
-                   treeish=c.repo.get_commit())
+       c.repo.archive(name, path)
 
 def build_rootfs(clean=True, rootfs_only=False, jffs2_only=False, devrootfs_only=False):
        parse_config('rootfs')
diff --git a/matrix/repositories.py b/matrix/repositories.py
new file mode 100644 (file)
index 0000000..aa405c8
--- /dev/null
@@ -0,0 +1,138 @@
+# Copyright (C) 2006-2008 Movial Oy
+# Authors: Timo Savola <tsavola@movial.fi>
+
+import os
+
+import config
+import git
+import log
+
+Error = RuntimeError
+
+class Repository(object):
+       def __init__(self, name, path, commits, exclude=None):
+               self.name = name
+               self.path = path
+               self.commits = commits
+               self.exclude = exclude
+               self.url = None
+               self.hash = None
+
+       def get_commit(self):
+               return self.commits.get(config.board) or self.commits.get(None)
+
+       def init(self):
+               if git.contains_database(self.path):
+                       url = git.getvar(self.path, 'remote.origin.url')
+                       if url:
+                               self.url = url
+                               if config.debug:
+                                       print 'Using', self.url
+                               return
+
+               for root in config.roots:
+                       for suffix in ('.git', ''):
+                               url = '%s/%s%s' % (root, self.name, suffix)
+                               if config.debug:
+                                       print 'Trying', url
+
+                               if not git.peek_remote(url, quiet=True):
+                                       continue
+
+                               self.url = url
+                               if config.debug:
+                                       print 'Found', url
+                               return
+
+               raise Error('Failed to locate repository: ' + self.name)
+
+       def exists(self):
+               return git.contains_database(self.path)
+
+       def clone(self):
+               print 'Cloning', self.path
+
+               if os.path.exists(self.path):
+                       self.__clone_in_place()
+               else:
+                       git.clone(self.path, self.url, checkout=False)
+
+               if self.exclude:
+                       git.exclude(self.path, self.exclude)
+
+               git.reset(self.path, self.get_commit(), hard=True)
+
+       def __clone_in_place(self):
+               tmp = os.path.join(self.path, 'tmp')
+               git.clone(tmp, self.url, checkout=False)
+
+               try:
+                       tmpdb = git.database_path(tmp)
+                       db = git.database_path(self.path)
+
+                       if config.debug:
+                               print 'Moving git database to', self.path
+
+                       os.rename(tmpdb, db)
+               finally:
+                       os.rmdir(tmp)
+
+       def rebase(self):
+               print 'Rebasing', self.path
+
+               git.remote_update(self.path)
+               git.rebase(self.path)
+
+       def pull(self):
+               print 'Pulling', self.path
+
+               git.pull(self.path)
+
+       def other_files(self):
+               return git.ls_files(self.path, ['-o'])
+
+       def dirty_files(self):
+               return git.ls_files(self.path, ['-m', '-d'])
+
+       def clean(self):
+               print 'Cleaning', self.path
+
+               paths = [os.path.join(self.path,i) for i in self.other_files()]
+               paths.sort()
+               paths.reverse()
+
+               for path in paths:
+                       if git.contains_database(path):
+                               continue
+
+                       if config.debug:
+                               print 'Removing', path
+
+                       if os.path.islink(path) or not os.path.isdir(path):
+                               os.remove(path)
+                       else:
+                               remove_tree(path)
+
+       def get_hash(self):
+               if not self.hash:
+                       self.hash = git.rev_parse(self.path, 'HEAD')
+               return self.hash
+
+       def describe(self):
+               return git.describe(self.path)
+
+       def archive(self, name, path):
+               print 'Archiving', self.path
+
+               git.archive(self.path, path,
+                           prefix=name + os.path.sep,
+                           treeish=self.get_commit())
+
+       def dump_log(self, path):
+               print 'Generating change log for', self.path
+
+               fd = os.open(path, os.O_WRONLY | os.O_CREAT, 0644)
+               try:
+                       git.log(self.path, [self.get_commit()], fd=fd)
+               finally:
+                       os.close(fd)