comparison python/hg-merge.py @ 207:7bad4b7281f2

add a file to merge hg repositories
author Jeff Hammel <jhammel@mozilla.com>
date Mon, 13 Feb 2012 16:22:59 -0800
parents
children
comparison
equal deleted inserted replaced
195:171bd3b71e84 207:7bad4b7281f2
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()