~fginther/charms/precise/juju-local/ppa-pinning

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))