With rules of nested paths, the order of rules matters.
Note: this does not make dirsync handle recursive changes. i.e. a mirror
from path1 to path2 won't trigger a rule matching path2.
durham |
Restricted Project |
With rules of nested paths, the order of rules matters.
Note: this does not make dirsync handle recursive changes. i.e. a mirror
from path1 to path2 won't trigger a rule matching path2.
Lint Skipped |
Unit Tests Skipped |
# dirsync.py - keep two directories synchronized at commit time | # dirsync.py - keep two directories synchronized at commit time | ||||
# | # | ||||
# Copyright 2015 Facebook, Inc. | # Copyright 2015 Facebook, Inc. | ||||
# | # | ||||
# This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
""" | """ | ||||
dirsync is an extension for keeping directories in a repo synchronized. | keep directories in a repo synchronized (DEPRECATED) | ||||
Configure it by adding the following config options to your .hg/hgrc. | Configure it by adding the following config options to your .hg/hgrc or | ||||
.hgdirsync in the root of the repo:: | |||||
[dirsync] | [dirsync] | ||||
projectX.dir1 = path/to/dir1 | projectX.dir1 = path/to/dir1 | ||||
projectX.dir2 = path/dir2 | projectX.dir2 = path/dir2 | ||||
The configs are of the form "group.name = path-to-dir". Every config entry with | The configs are of the form "group.name = path-to-dir". Every config entry with | ||||
the same `group` will be mirrored amongst each other. The `name` is just used to | the same `group` will be mirrored amongst each other. The `name` is just used to | ||||
separate them and is not used anywhere. The `path` is the path to the directory | separate them and is not used anywhere. The `path` is the path to the directory | ||||
from the repo root. It must be a directory, but it doesn't matter if you specify | from the repo root. It must be a directory, but it doesn't matter if you specify | ||||
the trailing '/' or not. | the trailing '/' or not. | ||||
Multiple mirror groups can be specified at once, and you can mirror between an | Multiple mirror groups can be specified at once, and you can mirror between an | ||||
arbitrary number of directories. Ex: | arbitrary number of directories:: | ||||
[dirsync] | [dirsync] | ||||
projectX.dir1 = path/to/dir1 | projectX.dir1 = path/to/dir1 | ||||
projectX.dir2 = path/dir2 | projectX.dir2 = path/dir2 | ||||
projectY.dir1 = otherpath/dir1 | projectY.dir1 = otherpath/dir1 | ||||
projectY.dir2 = foo/bar | projectY.dir2 = foo/bar | ||||
projectY.dir3 = foo/goo/hoo | projectY.dir3 = foo/goo/hoo | ||||
""" | """ | ||||
from __future__ import absolute_import | from __future__ import absolute_import | ||||
from collections import defaultdict | |||||
import errno | import errno | ||||
from mercurial import ( | from mercurial import ( | ||||
cmdutil, | cmdutil, | ||||
error, | error, | ||||
extensions, | extensions, | ||||
localrepo, | localrepo, | ||||
match as matchmod, | match as matchmod, | ||||
scmutil, | scmutil, | ||||
ui = repo.ui.__class__.copy(repo.ui) | ui = repo.ui.__class__.copy(repo.ui) | ||||
# also read from wvfs/.hgdirsync | # also read from wvfs/.hgdirsync | ||||
filename = '.hgdirsync' | filename = '.hgdirsync' | ||||
content = repo.wvfs.tryread(filename) | content = repo.wvfs.tryread(filename) | ||||
if content: | if content: | ||||
ui._tcfg.parse(filename, '[dirsync]\n%s' % content, ['dirsync']) | ui._tcfg.parse(filename, '[dirsync]\n%s' % content, ['dirsync']) | ||||
maps = defaultdict(list) | maps = util.sortdict() | ||||
for key, value in ui.configitems('dirsync'): | for key, value in ui.configitems('dirsync'): | ||||
if '.' not in key: | if '.' not in key: | ||||
continue | continue | ||||
name, disambig = key.split('.', 1) | name, disambig = key.split('.', 1) | ||||
# Normalize paths to have / at the end. For easy concatenation later. | # Normalize paths to have / at the end. For easy concatenation later. | ||||
if value[-1] != '/': | if value[-1] != '/': | ||||
value = value + '/' | value = value + '/' | ||||
if name not in maps: | |||||
maps[name] = [] | |||||
maps[name].append(value) | maps[name].append(value) | ||||
return maps | return maps | ||||
def getmirrors(maps, filename): | def getmirrors(maps, filename): | ||||
for key, mirrordirs in maps.iteritems(): | for key, mirrordirs in maps.iteritems(): | ||||
for subdir in mirrordirs: | for subdir in mirrordirs: | ||||
if filename.startswith(subdir): | if filename.startswith(subdir): | ||||
return mirrordirs | return mirrordirs |
> EOF | > EOF | ||||
$ echo c >> c | $ echo c >> c | ||||
$ hg commit -m 'modify group1 again' | $ hg commit -m 'modify group1 again' | ||||
mirrored changes in 'dir1/c' to 'dir2/c' | mirrored changes in 'dir1/c' to 'dir2/c' | ||||
mirrored changes in 'dir1/c' to 'dir6/c' | mirrored changes in 'dir1/c' to 'dir6/c' | ||||
mirrored changes in 'dir1/c' to 'dir7/c' | mirrored changes in 'dir1/c' to 'dir7/c' | ||||
$ cd ../.. | $ cd ../.. | ||||
Rule order matters. Only the first one gets executed. | |||||
$ hg init $TESTTMP/repo-order1 | |||||
$ cd $TESTTMP/repo-order1 | |||||
$ cat >> .hgdirsync <<'EOF' | |||||
> a.dir1 = a/ | |||||
> a.dir2 = b/ | |||||
> c.dir1 = a/c/ | |||||
> c.dir2 = c/ | |||||
> EOF | |||||
$ mkdir -p a/c | |||||
$ echo 1 > a/c/1 | |||||
$ hg commit -m 'order test' -A a | |||||
adding a/c/1 | |||||
mirrored adding 'a/c/1' to 'b/c/1' | |||||
$ hg init $TESTTMP/repo-order2 | |||||
$ cd $TESTTMP/repo-order2 | |||||
$ cat >> .hgdirsync <<'EOF' | |||||
> c.dir1 = a/c/ | |||||
> c.dir2 = c/ | |||||
> a.dir1 = a/ | |||||
> a.dir2 = b/ | |||||
> EOF | |||||
$ mkdir -p a/c | |||||
$ echo 1 > a/c/1 | |||||
$ hg commit -m 'order test' -A a | |||||
adding a/c/1 | |||||
mirrored adding 'a/c/1' to 'c/1' |