How can I create a patch file in mercurial that also contains changes from the contained sub-files

We work with several people in one project and use Mercurial as our DVCS. Our project has several subheadings. We need to send patches to each other by mail, because at this moment it is impossible to click and pull from the master repo. The export command - if it is executed on the main one, will only create a patch for the wizard, and not for subrepo. We could manually create templates for them, but we would like to know if there is an easier way to do this?

+4
source share
3 answers

As Martin said, I wrote my own extension. I will post the code here, in the hope that someone will improve it or make it defaulf available in mercurial ...

Thank you, Martin, I based it on your onsub extension, and yes, I know that there are several problems with it, but at the moment it serves its purpose. (problems with more than 10 subrepos and more than 1 level of nesting)

"""execute the Bundle command in a repository and each subrepository""" # bundlesb.py - execute the Bundle command in a repository and each subrepository # # Copyright 2012 Johan G. # # This software may be used and distributed according to the terms of # the GNU General Public License version 2 or any later version. import os import zipfile from mercurial.i18n import _ from mercurial import extensions, subrepo, util def bundlesb(ui, repo, *args, **opts): """execute the Bundle command in a repository and each subrepository Creates a combined bundle (with hgs extention) for the current repository. Because the revision numbers of the root and the subrepos will differ, we cannot reliably choose a revision to start from. A date to start from should be nice, but i have not taken the time to implement this. Instead i choose to take the N (default=10) last changesets into account. This seems to work well in our environment. Instead of providing the number of changesets for the operation, you can also specify -a to include all changesets. Use --verbose/-v to print information and the subrepo name for each subrepo. """ ui.status("Starting operation\n") zipname = os.path.splitext(' '.join(args))[0] if (zipname==''): zipname = os.path.join(repo.root, os.path.split(repo.root)[1]) #raise ValueError("FILE cannot be empty") zipname= zipname + '.hgs' allchangesets=opts.get('all') changesets=opts.get('changesets') ui.debug(_("input filename=%s ; AllChangesets=%s ; Changesets=%s \n") % (zipname, allchangesets, changesets)) files=[] #work on the root repository runcmd(ui, repo.root, files, "0Root", repo['.'].rev(), ".", changesets, allchangesets) #do the same for each subrepository foreach(ui, repo, files, changesets, allchangesets) # open the zip file for writing, and write stuff to it ui.status("creating file: " + zipname + "\n\n") file = zipfile.ZipFile(zipname, "w" ) for name in files: file.write(name, os.path.basename(name), zipfile.ZIP_DEFLATED) file.close() # open the file again, to see what in it file = zipfile.ZipFile(zipname, "r") for info in file.infolist(): ui.debug(info.filename + " " + str(info.date_time) + " " + str(info.file_size) + " " + str(info.compress_size) +"\n") #delete all the compressed .hg files os.remove(os.path.join(repo.root, info.filename)) ui.status("\nOperation complete\n") def foreach(ui, repo, files, changesets, allchangesets): ctx = repo['.'] work = [(1, ctx.sub(subpath)) for subpath in sorted(ctx.substate)] while work: (depth, sub) = work.pop(0) if hasattr(subrepo, 'relpath'): relpath = subrepo.relpath(sub) else: relpath = subrepo.subrelpath(sub) rev=sub._repo[sub._state[1]].rev() ui.debug(str(rev) + " " + str(sub._repo[sub._state[1]].user()) + " " + str(sub._repo[sub._state[1]].date()) + "\n") if depth>1: raise Exception("No support for nested levels deeper than 1 yet.") runcmd(ui, repo.root, files, str(depth) + relpath, rev, relpath, changesets, allchangesets) if isinstance(sub, subrepo.hgsubrepo): rev = sub._state[1] ctx = sub._repo[rev] w = [(depth + 1, ctx.sub(subpath)) for subpath in sorted(ctx.substate)] work.extend(w) def runcmd(ui, root, files, name, revision, path, changesets, allchangesets): files.append(root + "/" + name + ".hg") if (revision<=changesets) or allchangesets: cmd="hg bundle -a " + root + "/" + name + ".hg" else: cmd="hg bundle --base " + str(revision-changesets)+ " " + root + "/" + name + ".hg" ui.note(_("Working on '%s' in %s\n") % (path, root)) ui.debug( "command line: "+ cmd +"\n") util.system(cmd, cwd=os.path.join(root, path), onerr=util.Abort, errprefix=_('terminated bundlesub in %s') % path) cmdtable = { "bundlesb": (bundlesb, [('c', 'changesets', 10, _('the number of recent changesets to include in the bundle'), 'N'), ('a', 'all', None, _('include all changesets in the bundle')),], _('[-c|-a] FILE...')) } 

And vice versa:

  """execute the UnBundle command in a repository and each subrepository for a file created with BundleSb""" # unbundlesub.py - execute the UnBundle command in a repository and each subrepository # # Copyright 2012 Johan G. # # This software may be used and distributed according to the terms of # the GNU General Public License version 2 or any later version. import os import zipfile #import glob from mercurial.i18n import _ from mercurial import extensions, subrepo, util def unbundlesb(ui, repo, *args, **opts): """execute the UnBundle command in a repository and each subrepository for a file created with BundleSb Updates the current repository from a combined bundle (with hgs extention). Use --verbose/-v to print more detailed information during the operation. """ ui.status("Starting unbundle operation\n") update = opts.get('update') file = os.path.splitext(' '.join(args))[0] + '.hgs' if (file==''): raise ValueError("FILE cannot be empty") ui.debug("input filename=" + file + "\n") zfile = zipfile.ZipFile(file, "r" ) for info in zfile.infolist(): ui.debug(info.filename + " " + str(info.date_time) + " " + str(info.file_size) + " " + str(info.compress_size) +"\n") zfile.extract(info,repo.root) runcmd(ui, repo.root, info.filename, update) #delete all the compressed .hg files os.remove(os.path.join(repo.root, info.filename)) zfile.close() ui.status("\nOperation complete\n") def runcmd(ui, root, name, update): level=name[0] rep=name[1:len(name)-3] ui.debug(_("Detected level=%s for repository %s \n") % (level, rep)) cmd="hg unbundle " if (update): cmd= cmd + "-u " cmd= cmd + root + "\\" + name ui.note(_("Working on '%s' in %s\n") % (rep, root)) if (level == '1'): wd=os.path.join(root, rep) elif (level=='0'): wd=root else: raise Exception("Do not know what to do with a level >1") ui.debug(_("command line: %s in working directory %s\n") % (cmd, wd)) util.system(cmd, cwd=wd, onerr=util.Abort, errprefix=_('terminated unbundlesub in %s') % rep) cmdtable = { "unbundlesb": (unbundlesb, [('u', 'update', None, _('update to new branch head if changesets were unbundled'))], _('[-u] FILE...')) } 
0
source

There is no built-in support for this, as you have noticed.

Perhaps you can use the onsub extension that I wrote. This makes it easy to create patches for subppos:

 $ hg onsub 'hg export -r tip -o $HG_REPO/sub-$HG_SUBPATH.diff' 

The HG_SUBPATH variable HG_SUBPATH replaced with the path to the backwater.

This fails if you have nested props, since hg export cannot write the patch names sub-foo/bar/baz.diff . Therefore, you need to massage the path and replace / with - or similar. You should be able to import patches with a similar hg onsub .

If you earn it, feel free to add a note about it on the wiki page. You can also find out if you can extend the onsub extension to simplify this, perhaps by adding an option to make onsub also run on the top-level repo, and a new variable that can be used directly instead of munging HG_SUBPATH .

+1
source
  • The hg archive knows about subrepo: you can export changes from one set of changes (and they go recursively if subrepos are affected by commit)
  • hg diff also knows about subrepos
  • in the case of hg commit --subrepos this commit should include all the affected files from the subitems, so the export will export (?) these files as well
0
source

All Articles