RFC: A mercurial pack extension

John Mulligan phlogistonjohn at asynchrono.us
Sun Feb 17 11:35:05 CST 2008


Hello list,

I recently wrote an extension and was hoping to get some feedback on it. This 
extension is called 'pack' and creates whole-history bundles, and can clone 
from them. It doesn't do anything that couldn't be done before with some 
manual work, but I felt the urge to automate the process.

It creates two new sub-commands 'pack' and 'unpack' which create the bundle 
and clone from it respectively. The clone sub-command is also extended to 
support bundle sources in the form 'bundle:<filename>' and bundle+<url>. This 
clone operates the same as unpack with the bundle prefix removed. I would 
especially appreciate feedback on that syntax, I'm not sure if it's very 
mercurial-esque.

This extension is intended to be useful to users of static web hosting, ftp, 
IM, etc. or other situations where ssh or http are not appropriate/available, 
and distributing an entire directory tree would be inconvenient. There is a 
feature request/suggestion on the Mercurial wiki of allowing clone from a 
bundle. Also, I remember some issues on the mailing list with zip files and 
bundles were suggested but I had to hunt on the wiki to figure out how to get 
one that had all the changesets in it.

I've attached the single file to this mail, and my public repo is located at:
    http://hg.asynchrono.us/mercurial-pack/
 

Thanks for your time,
John M.

-------------- next part --------------
# pack.py : Create/clone whole repositories from bundles
#
# Author: John Mulligan <phlogistonjohn at asynchrono.us>
# License to distribute, given by the GNU GPL version 2 and above
# http://www.gnu.org/licenses/gpl.html
#

"""Create/clone whole repositories from bundles

This extension is intended to be useful to users of static web hosting,
ftp, IM, etc. or other media where ssh or http are not appropriate, and
distributing an entire directory tree would be inconvenient.

It provides two new commands: pack and unpack. These operate on mercurial
bundles that contain the entire history of the repository. Additionally,
the clone command will now support SOURCEs of the form 'bundle:<filename>'
and bundle+<url>. This clone operates the same as unpack with the bundle
prefix removed.
"""

__version__ = 0.2

from mercurial import hg
from mercurial import cmdutil
from mercurial import commands
from mercurial import repo,localrepo

if hasattr( commands, 'dispatch' ):
    dispatch = commands
else:
    # new for mercurial 0.9.5
    from mercurial import dispatch

import os, sys, re, stat
import urllib2, tempfile


def pack( ui, repo, outgoing, **opts ):
    """create a bundle containing the entire history of the repository

    Produce a bundle that contains all of the changesets in the repository, 
    creating a single file that can be used in lieu of a tar or zip file. 
    Use the unpack or clone commands on the destination to create a new 
    repository from the contents of the packed bundle.
    """
    return dispatch.dispatch( ['-R', repo.root, 'bundle', 
                               '--base', 'null', outgoing] )


def dl_bundle( url, bufsize=1024 ):
    """Given the URL of a bundle fetch the file and return the
    temporary filename for it
    """
    infh = urllib2.urlopen(url)
    outfd,name = tempfile.mkstemp()
    outfh = os.fdopen(outfd,'w')
    while True:
        buf = infh.read(bufsize)
        if not buf:
            break
        outfh.write(buf)
    infh.close()
    outfh.close()
    return name 

def unpack( ui, incoming, newrepo=None, **opts ):
    """create a new repository from the contents of a bundle

    Given a bundle that has an entire repository's history, unpack will
    create a new repo "cloned" from that bundle.

    If no directory is given, the current directory is used.

    You can also use: hg clone bundle:FILE [DEST]
    """
    cleanup = False
    if not newrepo:
        newrepo = '.'
    if '://' in incoming:
        incoming = dl_bundle(incoming)
        cleanup = True
    try:
        localrepo.localrepository(ui,path=newrepo)
    except repo.RepoError, ee:
        localrepo.localrepository(ui,path=newrepo,create=1)

    st = dispatch.dispatch( ['-R', newrepo, 'unbundle', '-u', incoming] )
    if cleanup:
        os.unlink( incoming )
    return st


def clone_with_pack( ui, source, dest=None, **opts ):
    if source.startswith('bundle+'):
        # url form
        return unpack( ui, source[7:], dest, **opts )
    if source.startswith('bundle:'):
        # local bundle form
        return unpack( ui, source.split(':',1)[-1], dest, **opts )
    return _orig_clone(ui,source,dest,**opts)
    



cmdtable = {
    "pack" : ( 
        pack,
        [],
        "hg pack OUTGOING" ),

    "unpack" : (
        unpack,
        [],
        "hg unpack INCOMING [DEST]"  ),
}

# don't barf if unpack is used outside of a repo
commands.norepo += ( " unpack " )

_orig_clone = commands.table['^clone'][0]

try:
    clone_with_pack.__doc__ = _orig_clone.__doc__
except:
    pass

# patch in the new version of clone
commands.table['^clone'] = ( clone_with_pack, commands.table['^clone'][1], 
                                           commands.table['^clone'][2] )



More information about the Mercurial mailing list