25
|
1 #!/usr/bin/env python
|
|
2
|
|
3 """
|
|
4 merge mercurial repositories
|
|
5
|
|
6 Example:
|
|
7 hg-merge.py --master http://hg.mozilla.org/build/talos http://hg.mozilla.org/build/pageloader#talos/pageloader
|
|
8 """
|
|
9
|
|
10 import optparse
|
|
11 import os
|
|
12 import shutil
|
|
13 import subprocess
|
|
14 import sys
|
|
15 import tempfile
|
|
16 import urlparse
|
|
17 try:
|
|
18 from subprocess import check_call as call
|
|
19 except:
|
|
20 from subprocess import call
|
|
21
|
|
22 def url2filename(url):
|
|
23 """gets a filename from a url"""
|
|
24 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
|
|
25 path = path.rstrip('/')
|
|
26 assert path and '/' in path
|
|
27 return path.split('/')[-1]
|
|
28
|
|
29 def manifest(hgrepo):
|
|
30 """manifest of a local hg repository"""
|
|
31 process = subprocess.Popen(['hg', 'manifest'], cwd=hgrepo, stdout=subprocess.PIPE)
|
|
32 stdout, stderr = process.communicate()
|
|
33 assert not process.returncode
|
|
34 manifest = stdout.strip().splitlines()
|
|
35 return set(manifest)
|
|
36
|
|
37 def merge(hgroot, repo, subpath=None):
|
|
38 """merge repo to hgroot at subpath (None for repository root)"""
|
|
39
|
|
40 # get a manifest of the current repository
|
|
41 root_manifest = manifest(hgroot)
|
|
42 toplevel_contents = os.listdir(hgroot)
|
|
43
|
|
44 # staging area
|
|
45 tempdir = tempfile.mkdtemp()
|
|
46 fd, tmpfile = tempfile.mkstemp()
|
|
47 os.close(fd)
|
|
48
|
|
49 exception = None
|
|
50 try:
|
|
51
|
|
52 # clone the repository to be merged in
|
|
53 call(['hg', 'clone', repo, tempdir])
|
|
54
|
|
55 # check manifest for conflicts
|
|
56 repo_manifest = manifest(tempdir)
|
|
57 assert repo_manifest, "Empty repository: %s" % repo
|
|
58 intersection = root_manifest.intersection(repo_manifest)
|
|
59 assert not intersection, "Overlap between %s and %s: %s" % (hgroot, repo, intersection)
|
|
60
|
|
61 # create a bundle
|
|
62 call(['hg', 'bundle', tmpfile, '--all'], cwd=tempdir)
|
|
63
|
|
64 # apply the bundle
|
|
65 call(['hg', 'unbundle', tmpfile], cwd=hgroot)
|
|
66 call(['hg', 'merge'], cwd=hgroot)
|
|
67
|
|
68 if subpath:
|
|
69 # move the new files to their new locations
|
|
70 for item in repo_manifest:
|
|
71 path = os.path.join(subpath, item)
|
|
72 fullpath = os.path.join(hgroot, path)
|
|
73 assert not os.path.exists(fullpath), "%s already exists" % fullpath
|
|
74 subdirectory = os.path.dirname(fullpath)
|
|
75 if not os.path.exists(subdirectory):
|
|
76 os.makedirs(subdirectory)
|
|
77 call(['hg', 'mv', item, path], cwd=hgroot)
|
|
78 call(['hg', 'commit', '-m', 'merge %s to %s' % (repo, subpath)], cwd=hgroot)
|
|
79 else:
|
|
80 call(['hg', 'commit', '-m', 'merge in %s' % repo], cwd=hgroot)
|
|
81
|
|
82 except Exception, exception:
|
|
83 pass # reraise on cleanup
|
|
84
|
|
85 # cleanup
|
|
86 shutil.rmtree(tempdir)
|
|
87 os.remove(tmpfile)
|
|
88 if exception is not None:
|
|
89 raise exception
|
|
90
|
|
91 def main(args=sys.argv[1:]):
|
|
92
|
|
93 # parse command line options
|
|
94 usage = "%prog [options] http://hg.example.com/repository/path#destination/path [...]"
|
|
95 parser = optparse.OptionParser(usage=usage, description=__doc__)
|
|
96 parser.add_option('-m', '--master', dest='master',
|
|
97 help="use this as the master repository (new clone, otherwise use CWD)")
|
|
98 options, args = parser.parse_args(args)
|
|
99 if not args:
|
|
100 parser.print_help()
|
|
101 parser.exit()
|
|
102
|
|
103 if options.master:
|
|
104 # clone the new repository
|
|
105 directory = url2filename(options.master)
|
|
106 if os.path.exists(directory):
|
|
107 shutil.rmtree(directory)
|
|
108 call(['hg', 'clone', options.master])
|
|
109 hgroot = os.path.join(os.getcwd(), directory)
|
|
110 else:
|
|
111 # get the root of the repository
|
|
112 process = subprocess.Popen(['hg', 'root'], stdout=subprocess.PIPE)
|
|
113 hgroot, stderr = process.communicate()
|
|
114 hgroot = hgroot.strip()
|
|
115 if process.returncode:
|
|
116 sys.exit(1)
|
|
117 assert os.path.exists(hgroot) and os.path.isdir(hgroot), "%s not found" % hgroot
|
|
118
|
|
119 # get the other repos to add
|
|
120 for repo in args:
|
|
121 subpath = None
|
|
122 if '#' in repo:
|
|
123 repo, subpath = repo.rsplit('#', 1)
|
|
124 merge(hgroot, repo, subpath)
|
|
125
|
|
126 if __name__ == '__main__':
|
|
127 main()
|