sub-repository extension
Azra Aiyl
azraiyl at gmail.com
Wed Nov 5 16:38:11 UTC 2008
I've created a small (not bullet-proof) sub-repository extension for
mercurial.
The company I work for, has more than user that only likes to use TortoiseHg or
two or three commands on the command line. The forest extensions therefore is
overkill and AFAIK not integrated into Eclipse/Tortoise or any GUI. Here is the
basic idea how it works:
You have a repository were you normally work that needs some libraries in
another repository. You add this sub-repository inside your current
main-repository. If you don't have any extension the sub-repositories have to be
updated manually by everyone to the correct revision. This extensions only adds
some commands to this ideas:
subrepofind -> Search inside the current repository if there is any other
repository. If there is any repository print the source location, the name and
the current revision of this repository. This command is not really needed, you
can do this manually as well.
subrepoadd -> Add any sub-repository to the file .hgsubrepo. The source
location, name and current revision is added to this file. If you like you can
modify this file manually, it is readable. Command is therefore as
usefull/useless as hg tag.
subrepogather -> Search through every repository in .hgsubrepo and update the
revision to the most current revision. This command is normally called with a
hook automatically.
The extension adds a precommit hook and an update hook. There is also a hack to
dynamically add a preprecommit hook. The downside of the normal precommit hook
is that it is only called if there is any change in the current repository and
the hook can't see if there are any changes made in the precommit hook itself.
This preprecommit is called everytime.
How does it work?
Whenever anyone commits a change to main-repository the preprecommit hook
updates the .hgsubrepo file in the main-repository with the latest revision of
all previsouly registered sub-repositories. Hg itselfs checks if the file does
not change, therefore it does not matter if we rewrite this file every time.
Then the precommit hook is called. It checks if there is any modification in any
sub-repository. If there is any modification a commit to the main-repository is
disallowed. If you don't like this behaviour just remove the precommit hook.
Whenever you update your main-repository all sub-repository are updated
according to the .hgsubrepo file.
There is only (to some extent) clever idea here. If you manually update a
sub-repository here to a new revision and commit the main-repository, the
extension rewrites the .hgsubrepo file and then commits the new revision. If
you push this modification anyone that pulls and updates the repository
automagically has updated sub-repositories. Because of this it integrates not
that bad into an existing environment without the need to add a new commands to
existing scripts.
If anyone is interested in this I'm open for ideas, improvements and
maybe writing a short tutorial.
Azraiyl
[Mercurial.ini]
subrepo = subrepo.py
[hooks]
precommit = python:subrepo.precommit
update = python:subrepo.update
[subrepo.py]
import string
import sys
import os
import os.path
from mercurial import hg, node, localrepo, util
def readconfig(repo):
"""read a subrepo file"""
subrepos = []
filename = os.path.join(repo.root, ".hgsubrepo")
if os.path.exists(filename):
for line in open(filename, "rb").readlines():
if (not line.startswith("#")) and (not line.startswith(";")) and
(len(line) >= len("? ? ?")):
line = line.replace("\t", " ").replace("=", " ")
tokens = line.split(" ")
source = tokens[0].strip()
name = tokens[1].strip()
rev = tokens[2].strip()
subrepos += [ [ source, name, rev ] ]
return subrepos
def writeconfig(repo, subrepos):
"""write subrepo list to a file and remove duplicates"""
filename = os.path.join(repo.root, ".hgsubrepo")
content = ""
unique = []
for subrepo in subrepos:
if subrepo[1] not in unique:
content += "%s %s %s\n" % tuple(subrepo)
unique += [ subrepo[1] ]
# do not create a file if there is no subrepo
if len(content) > 0:
open(filename, "wb").write(content)
def repocurrent(repo):
"""get current revision"""
return node.hex(repo.parents()[0].node())
def repotip(repo):
"""get tip revision"""
return node.hex(repo.heads()[0])
def precommit(ui, repo, parent1, parent2, hooktype):
"""check if there are any uncommited changes inside any sub repository"""
#print "[PreCommit Hook I]"
#print "ui", [ui]
#print "repo", [repo]
#print "parent1", [parent1]
#print "parent2", [parent2]
#print "hooktype", [hooktype]
subrepos = []
for subrepo in readconfig(repo):
subreposource, subreponame, subreporev = subrepo
subrepopath = os.path.join(repo.root, subreponame)
subrepo = hg.repository(ui, subrepopath)
modified, added, removed, deleted = subrepo.status()[:4]
if modified or added or removed or deleted:
ui.write("sub repository \"%s\" has modifications!\n" % (subreponame))
return True
#print "[PreCommit Hook O]"
#return True # Abort
def update(ui, repo, parent1, parent2, hooktype, error):
"""update (and clone if necessary) all sub repositories"""
#print "[Update Hook I]"
#print "ui", [ui]
#print "repo", [repo]
#print "parent1", [parent1]
#print "parent2", [parent2]
#print "hooktype", [hooktype]
#print "erro", [error]
for subrepo in readconfig(repo):
subreposource, subreponame, subreporev = subrepo
subrepopath = os.path.join(repo.root, subreponame)
if not os.path.exists(subrepopath):
hg.clone(ui, subreposource, subrepopath) # see hg.py for more parameters
subrepo = hg.repository(ui, subrepopath)
modified, added, removed, deleted = subrepo.status()[:4]
if modified or added or removed or deleted:
ui.write("sub repository \"%s\" has modifications! no update!\n" %
(subreponame))
else:
hg.update(subrepo, node.bin(subreporev))
#print "[Update Hook O]"
#return True # Abort
def subrepogather(ui, repo, *dirs, **opts):
"""get the revision of all known sub repositories and rewrite .hgsubrepo"""
#print "[SubRepoGather I]"
#print "ui", [ui]
#print "repo", [repo]
#print "dirs", [dirs]
#print "opts", [opts]
subrepos = []
for subrepo in readconfig(repo):
subreposource, subreponame, subreporev = subrepo
subrepopath = os.path.join(repo.root, subreponame)
subrepo = hg.repository(ui, subrepopath)
subreporev = repocurrent(subrepo)
subrepos += [ [ subreposource, subreponame, subreporev ] ]
writeconfig(repo, subrepos)
#print "[SubRepogather O]"
def subrepofind(ui, repo, *dirs, **opts):
"""try to find find any sub repository recurisvely"""
#print "[SubRepoFind I]"
#print "ui", [ui]
#print "repo", [repo]
#print "dirs", [dirs]
#print "opts", [opts]
ui.write("# source path revision\n")
for path, directories, files in os.walk(repo.root):
if (".hg" in directories) and (path != repo.root):
subrepo = hg.repository(ui, path)
# FIXME that is a bit hackish there is maybe a more generic way to
get a relative path
subreponame = subrepo.root[len(repo.root) + 1:]
#ui.write("Path: %s\n" % subrepo.root)
#ui.write("Source: %s\n" % subrepo.ui.expandpath("default"))
#ui.write("Current Revision: %s\n" % repocurrent(subrepo))
#ui.write("Tip Revision: %s\n" % repotip(subrepo))
ui.write("%s %s %s\n" % (subrepo.ui.expandpath("default"),
subreponame, repocurrent(subrepo)))
#print "[SubRepoFind O]"
def subrepoadd(ui, repo, *dirs, **opts):
"""add a subrepo to .hgsubrepo"""
#print "[SubRepoAdd I]"
#print "ui", [ui]
#print "repo", [repo]
#print "dirs", [dirs]
#print "opts", [opts]
if len(dirs) == 1:
subreponame = dirs[0]
ui.write("add sub repository \"%s\"\n" % subreponame)
subrepopath = os.path.join(repo.root, subreponame)
subrepo = hg.repository(ui, subrepopath)
subreposource = subrepo.ui.expandpath("default")
subreponame = subreponame
subreporev = repocurrent(subrepo)
subrepos = readconfig(repo)
subrepos += [ [ subreposource, subreponame, subreporev ] ]
writeconfig(repo, subrepos)
#print "[SubRepoAdd O]"
cmdtable = {
"subrepogather|srgather": (subrepogather, [], "hg subrepogather"),
"subrepofind|srfind": (subrepofind, [], "hg subrepofind"),
"subrepoadd|sradd": (subrepoadd, [], "hg subrepoadd [SUBREPOSITORY]"),
}
def preprecommit(ui, repo):
#print "[PrePreCommit Hook I]"
#print "ui", [ui]
#print "repo", [repo]
subrepogather(ui, repo)
#print "[PrePreCommit Hook O]"
return False
def commit(self, *args, **kwargs):
"""
Hack to get a real precommit hook. One that allows to modify files e.g.
The precommit hook is only called in localrepo.commit. I hope it is
safe to only decorate localrepo.commit.
"""
if preprecommit(self.ui, self):
raise util.Abort("preprecommit hook failed")
self.origcommit(*args, **kwargs)
localrepo.localrepository.origcommit = localrepo.localrepository.commit
localrepo.localrepository.commit = commit
More information about the Mercurial
mailing list