1
by Evan Dandrea
Initial commit of the juju-local charm. |
1 |
# Copyright 2014 Canonical Ltd. All rights reserved.
|
2 |
||
3 |
import os |
|
4 |
import re |
|
5 |
import sys |
|
6 |
import errno |
|
7 |
import hashlib |
|
8 |
import subprocess |
|
9 |
import optparse |
|
10 |
import urllib |
|
11 |
||
12 |
from os import curdir |
|
13 |
from bzrlib.branch import Branch |
|
14 |
from bzrlib.plugin import load_plugins |
|
15 |
load_plugins() |
|
16 |
from bzrlib.plugins.launchpad import account as lp_account |
|
17 |
||
18 |
if 'GlobalConfig' in dir(lp_account): |
|
19 |
from bzrlib.config import LocationConfig as LocationConfiguration |
|
20 |
_ = LocationConfiguration |
|
21 |
else: |
|
22 |
from bzrlib.config import LocationStack as LocationConfiguration |
|
23 |
_ = LocationConfiguration |
|
24 |
||
25 |
||
26 |
def get_ubunet_branch_config(config_file): |
|
27 |
"""
|
|
28 |
Retrieves the sourcedeps configuration for an ubunet source dir.
|
|
29 |
Returns a dict of (branch, revspec) tuples, keyed by branch name.
|
|
30 |
"""
|
|
31 |
branches = {} |
|
32 |
with open(config_file, 'r') as stream: |
|
33 |
for line in stream: |
|
34 |
line = line.split('#')[0].strip() |
|
35 |
match = re.match(r'(\S+)\s+' |
|
36 |
'bzr\+ssh://([^/]+)/([^;]+)'
|
|
37 |
'(?:;revno=(\d+))?', line) |
|
38 |
if match: |
|
39 |
name, host, branch, revno = match.group(1, 2, 3, 4) |
|
40 |
if revno is None: |
|
41 |
revspec = "-1" |
|
42 |
else: |
|
43 |
revspec = revno |
|
44 |
branches[name] = (host, branch, revspec) |
|
45 |
return branches |
|
46 |
||
47 |
||
48 |
def main(config_file, parent_dir, target_dir, verbose): |
|
49 |
"""Do the deed."""
|
|
50 |
||
51 |
try: |
|
52 |
os.makedirs(parent_dir) |
|
53 |
except OSError, e: |
|
54 |
if e.errno != errno.EEXIST: |
|
55 |
raise
|
|
56 |
||
57 |
bzr_config = LocationConfiguration(parent_dir) |
|
58 |
get_lp_login = lp_account.get_lp_login |
|
59 |
username = get_lp_login(bzr_config) or get_lp_login() |
|
60 |
if username is None: |
|
61 |
raise RuntimeError("Unable to determine launchpad login") |
|
62 |
quoted_username = urllib.quote(username) |
|
63 |
||
64 |
branches = sorted(get_ubunet_branch_config(config_file).iteritems()) |
|
65 |
for branch_name, (host, quoted_branch_spec, revspec) in branches: |
|
66 |
revno = int(revspec) |
|
67 |
||
68 |
# qualify mirror branch name with hash of remote repo path to deal
|
|
69 |
# with changes to the remote branch URL over time
|
|
70 |
branch_spec_digest = hashlib.sha1(quoted_branch_spec).hexdigest() |
|
71 |
branch_directory = branch_spec_digest |
|
72 |
||
73 |
source_path = os.path.join(parent_dir, branch_directory) |
|
74 |
destination_path = os.path.join(target_dir, branch_name) |
|
75 |
||
76 |
# Remove leftover symlinks/stray files.
|
|
77 |
try: |
|
78 |
os.remove(destination_path) |
|
79 |
except OSError, e: |
|
80 |
if e.errno != errno.EISDIR and e.errno != errno.ENOENT: |
|
81 |
raise
|
|
82 |
||
83 |
branch_url = ("bzr+ssh://%s@%s/%s" % |
|
84 |
(quoted_username, host, quoted_branch_spec)) |
|
85 |
lp_url = "lp:" + quoted_branch_spec.replace("+branch/", "") |
|
86 |
||
87 |
# Create the local mirror branch if it doesn't already exist
|
|
88 |
if verbose: |
|
89 |
sys.stderr.write('%30s: ' % (branch_name,)) |
|
90 |
sys.stderr.flush() |
|
91 |
||
92 |
fresh = False |
|
93 |
if not os.path.exists(source_path): |
|
94 |
subprocess.check_call(['bzr', 'branch', '-q', |
|
95 |
'--', branch_url, source_path]) |
|
96 |
fresh = True |
|
97 |
||
98 |
source_branch = Branch.open(source_path) |
|
99 |
||
100 |
# Freshen the source branch if required (-1 means we want tip).
|
|
101 |
if not fresh and (revno == -1 or revno > source_branch.revno()): |
|
102 |
subprocess.check_call(['bzr', 'pull', '-q', '--overwrite', '-r', |
|
103 |
str(revno), '-d', source_path, |
|
104 |
'--', branch_url]) |
|
105 |
||
106 |
if os.path.exists(destination_path): |
|
107 |
# Overwrite the destination with the appropriate revision.
|
|
108 |
subprocess.check_call(['bzr', 'clean-tree', '--force', '-q', |
|
109 |
'--ignored', '-d', destination_path]) |
|
110 |
subprocess.check_call(['bzr', 'pull', '-q', '--overwrite', |
|
111 |
'-r', str(revno), |
|
112 |
'-d', destination_path, '--', source_path]) |
|
113 |
else: |
|
114 |
# Create a new branch.
|
|
115 |
subprocess.check_call(['bzr', 'branch', '-q', '--hardlink', |
|
116 |
'-r', str(revno), |
|
117 |
'--', source_path, destination_path]) |
|
118 |
||
119 |
# Check the state of the destination branch.
|
|
120 |
destination_branch = Branch.open(destination_path) |
|
121 |
destination_revno = destination_branch.revno() |
|
122 |
||
123 |
if verbose: |
|
124 |
sys.stderr.write('checked out %4s of %s\n' % |
|
125 |
("r" + str(destination_revno), lp_url)) |
|
126 |
sys.stderr.flush() |
|
127 |
||
128 |
if revno != -1 and destination_revno != revno: |
|
129 |
raise RuntimeError("Expected revno %d but got revno %d" % |
|
130 |
(revno, destination_revno)) |
|
131 |
||
132 |
if __name__ == '__main__': |
|
133 |
parser = optparse.OptionParser( |
|
134 |
usage="%prog [options]", |
|
135 |
description=( |
|
136 |
"Add a lightweight checkout in <target> for each "
|
|
137 |
"corresponding file in <parent>."), |
|
138 |
add_help_option=False) |
|
139 |
parser.add_option( |
|
140 |
'-p', '--parent', dest='parent', default=None, |
|
141 |
help=("The directory of the parent tree."), |
|
142 |
metavar="DIR") |
|
143 |
parser.add_option( |
|
144 |
'-t', '--target', dest='target', default=curdir, |
|
145 |
help=("The directory of the target tree."), |
|
146 |
metavar="DIR") |
|
147 |
parser.add_option( |
|
148 |
'-c', '--config', dest='config', default=None, |
|
149 |
help=("The config file to be used for config-manager."), |
|
150 |
metavar="DIR") |
|
151 |
parser.add_option( |
|
152 |
'-q', '--quiet', dest='verbose', action='store_false', |
|
153 |
help="Be less verbose.") |
|
154 |
parser.add_option( |
|
155 |
'-h', '--help', action='help', |
|
156 |
help="Show this help message and exit.") |
|
157 |
parser.set_defaults(verbose=True) |
|
158 |
||
159 |
options, args = parser.parse_args() |
|
160 |
||
161 |
if options.parent is None: |
|
162 |
parser.error( |
|
163 |
"Parent directory not specified.") |
|
164 |
||
165 |
if options.target is None: |
|
166 |
parser.error( |
|
167 |
"Target directory not specified.") |
|
168 |
||
169 |
sys.exit(main(config_file=options.config, |
|
170 |
parent_dir=options.parent, |
|
171 |
target_dir=options.target, |
|
172 |
verbose=options.verbose)) |