Completed
Push — develop ( 3dabc3...f1379d )
by Jace
03:04 queued 17s
created

update()   D

Complexity

Conditions 8

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 8

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
c 2
b 0
f 0
dl 0
loc 45
ccs 18
cts 18
cp 1
crap 8
rs 4
1
"""Functions to manage the installation of dependencies."""
2
3 1
import os
4 1
import functools
5 1
import datetime
6 1
import logging
7
8 1
from . import common, system
9 1
from .models import load_config, Config, Source
10
11 1
log = logging.getLogger(__name__)
12
13
14 1
def restore_cwd(func):
15 1
    @functools.wraps(func)
16
    def wrapped(*args, **kwargs):
17 1
        cwd = os.getcwd()
18 1
        result = func(*args, **kwargs)
19 1
        os.chdir(cwd)
20 1
        return result
21 1
    return wrapped
22
23
24 1
def init():
25
    """Create a new configuration file for the project."""
26 1
    success = False
27
28 1
    config = load_config()
29 1
    if config:
30 1
        msg = "Configuration file already exists: {}".format(config.path)
31 1
        common.show(msg, 'error')
32
33
    else:
34 1
        config = Config()
35 1
        source = Source(name="sample_dependency",
36
                        repo="https://github.com/githubtraining/hellogitworld")
37 1
        config.sources.append(source)
38 1
        source = source.lock(rev="ebbbf773431ba07510251bb03f9525c7bab2b13a")
39 1
        config.sources_locked.append(source)
40 1
        config.save()
41
42 1
        msg = "Created sample configuration file: {}".format(config.path)
43 1
        common.show(msg, 'success')
44 1
        success = True
45
46 1
    common.show("To edit this configuration file, run: gitman edit", 'message')
47
48 1
    return success
49
50
51 1
@restore_cwd
52 1
def install(*names, root=None, depth=None,
53
            force=False, fetch=False, clean=True):
54
    """Install dependencies for a project.
55
56
    Optional arguments:
57
58
    - `*names`: optional list of dependency directory names to filter on
59
    - `root`: specifies the path to the root working tree
60
    - `depth`: number of levels of dependencies to traverse
61
    - `force`: indicates uncommitted changes can be overwritten and
62
               script errors can be ignored
63
    - `fetch`: indicates the latest branches should always be fetched
64
    - `clean`: indicates untracked files should be deleted from dependencies
65
66
    """
67 1
    log.info("%sInstalling dependencies: %s",
68
             'force-' if force else '',
69
             ', '.join(names) if names else '<all>')
70 1
    count = None
71
72 1
    root = _find_root(root)
73 1
    config = load_config(root)
74
75 1
    if config:
76 1
        common.newline()
77 1
        common.show("Installing dependencies...", color='message', log=False)
78 1
        common.newline()
79 1
        count = config.install_dependencies(
80
            *names, update=False, depth=depth,
81
            force=force, fetch=fetch, clean=clean,
82
        )
0 ignored issues
show
introduced by
invalid syntax
Loading history...
83
84 1
        if count:
85 1
            _run_scripts(*names, depth=depth, force=force, _config=config)
86
87 1
    return _display_result("install", "Installed", count)
88
89
90 1
@restore_cwd
91 1
def update(*names, root=None, depth=None,
92
           recurse=False, force=False, clean=True, lock=None):  # pylint: disable=redefined-outer-name
93
    """Update dependencies for a project.
94
95
    Optional arguments:
96
97
    - `*names`: optional list of dependency directory names to filter on
98
    - `root`: specifies the path to the root working tree
99
    - `depth`: number of levels of dependencies to traverse
100
    - `recurse`: indicates nested dependencies should also be updated
101
    - `force`: indicates uncommitted changes can be overwritten and
102
               script errors can be ignored
103
    - `clean`: indicates untracked files should be deleted from dependencies
104
    - `lock`: indicates actual dependency versions should be recorded
105
106
    """
107 1
    log.info("%s dependencies%s: %s",
108
             'Force updating' if force else 'Updating',
109
             ', recursively' if recurse else '',
110
             ', '.join(names) if names else '<all>')
111 1
    count = None
112
113 1
    root = _find_root(root)
114 1
    config = load_config(root)
115
116 1
    if config:
117 1
        common.newline()
118 1
        common.show("Updating dependencies...", color='message', log=False)
119 1
        common.newline()
120 1
        count = config.install_dependencies(
121
            *names, update=True, depth=depth,
122
            recurse=recurse, force=force, fetch=True, clean=clean,
123
        )
124
125 1
        if count and lock is not False:
126 1
            common.show("Recording installed versions...",
127
                        color='message', log=False)
128 1
            common.newline()
129 1
            config.lock_dependencies(*names, obey_existing=lock is None)
130
131 1
        if count:
132 1
            _run_scripts(*names, depth=depth, force=force, _config=config)
133
134 1
    return _display_result("update", "Updated", count)
135
136
137 1
def _run_scripts(*names, depth=None, force=False, _config=None):
138
    """Run post-install scripts.
139
140
    Optional arguments:
141
142
    - `*names`: optional list of dependency directory names filter on
143
    - `depth`: number of levels of dependencies to traverse
144
    - `force`: indicates script errors can be ignored
145
146
    """
147 1
    assert _config, "'_config' is required"
148
149 1
    common.show("Running scripts...", color='message', log=False)
150 1
    common.newline()
151 1
    _config.run_scripts(*names, depth=depth, force=force)
152
153
154 1
@restore_cwd
155 1
def display(*, root=None, depth=None, allow_dirty=True):
156
    """Display installed dependencies for a project.
157
158
    Optional arguments:
159
160
    - `root`: specifies the path to the root working tree
161
    - `depth`: number of levels of dependencies to traverse
162
    - `allow_dirty`: causes uncommitted changes to be ignored
163
164
    """
165 1
    log.info("Displaying dependencies...")
166 1
    count = None
167
168 1
    root = _find_root(root)
169 1
    config = load_config(root)
170
171 1
    if config:
172 1
        common.newline()
173 1
        common.show("Displaying current dependency versions...",
174
                    color='message', log=False)
175 1
        common.newline()
176 1
        config.log(datetime.datetime.now().strftime("%F %T"))
177 1
        count = 0
178 1
        for identity in config.get_dependencies(depth=depth,
179
                                                allow_dirty=allow_dirty):
180 1
            count += 1
181 1
            config.log("{}: {} @ {}", *identity)
182 1
        config.log()
183
184 1
    return _display_result("display", "Displayed", count)
185
186
187 1
@restore_cwd
188 1
def lock(*names, root=None):
189
    """Lock current dependency versions for a project.
190
191
    Optional arguments:
192
193
    - `*names`: optional list of dependency directory names to filter on
194
    - `root`: specifies the path to the root working tree
195
196
    """
197 1
    log.info("Locking dependencies...")
198 1
    count = None
199
200 1
    root = _find_root(root)
201 1
    config = load_config(root)
202
203 1
    if config:
204 1
        common.newline()
205 1
        common.show("Locking dependencies...", color='message', log=False)
206 1
        common.newline()
207 1
        count = config.lock_dependencies(*names, obey_existing=False)
208 1
        common.dedent(level=0)
209
210 1
    return _display_result("lock", "Locked", count)
211
212
213 1
@restore_cwd
214 1
def delete(*, root=None, force=False):
215
    """Delete dependencies for a project.
216
217
    Optional arguments:
218
219
    - `root`: specifies the path to the root working tree
220
    - `force`: indicates uncommitted changes can be overwritten
221
222
    """
223 1
    log.info("Deleting dependencies...")
224 1
    count = None
225
226 1
    root = _find_root(root)
227 1
    config = load_config(root)
228
229 1
    if config:
230 1
        common.newline()
231 1
        common.show("Checking for uncommitted changes...",
232
                    color='message', log=False)
233 1
        common.newline()
234 1
        count = len(list(config.get_dependencies(allow_dirty=force)))
235 1
        common.dedent(level=0)
236 1
        common.show("Deleting all dependencies...", color='message', log=False)
237 1
        common.newline()
238 1
        config.uninstall_dependencies()
239
240 1
    return _display_result("delete", "Deleted", count, allow_zero=True)
241
242
243 1
def show(*names, root=None):
244
    """Display the path of an installed dependency or internal file.
245
246
    - `name`: dependency name or internal file keyword
247
    - `root`: specifies the path to the root working tree
248
249
    """
250 1
    log.info("Finding paths...")
251
252 1
    root = _find_root(root)
253 1
    config = load_config(root)
254 1
    if not config:
255 1
        log.error("No configuration found")
256 1
        return False
257
258 1
    for name in names or [None]:
259 1
        common.show(config.get_path(name), color='path')
260
261 1
    return True
262
263
264 1
def edit(*, root=None):
265
    """Open the confuration file for a project.
266
267
    Optional arguments:
268
269
    - `root`: specifies the path to the root working tree
270
271
    """
272 1
    log.info("Launching configuration...")
273
274 1
    root = _find_root(root)
275 1
    config = load_config(root)
276 1
    if not config:
277 1
        log.error("No configuration found")
278 1
        return False
279
280 1
    return system.launch(config.path)
281
282
283 1
def _find_root(base, cwd=None):
284 1
    if cwd is None:
285 1
        cwd = os.getcwd()
286 1
        log.info("Current directory: %s", cwd)
287
288 1
    if base:
289 1
        root = os.path.abspath(base)
290 1
        log.info("Specified root: %s", root)
291
292
    else:
293 1
        log.info("Searching for root...")
294 1
        path = cwd
295 1
        prev = None
296 1
        root = None
297 1
        while path != prev:
298 1
            log.debug("Checking path: %s", path)
299 1
            if '.git' in os.listdir(path):
300 1
                root = path
301 1
                break
302 1
            prev = path
303 1
            path = os.path.dirname(path)
304
305 1
        if root:
306 1
            log.info("Found root: %s", root)
307
        else:
308 1
            root = cwd
309 1
            log.warning("No root found, default: %s", root)
310
311 1
    return root
312
313
314 1
def _display_result(present, past, count, allow_zero=False):
315
    """Convert a command's dependency count to a return status.
316
317
    >>> _display_result("sample", "Sampled", 1)
318
    True
319
320
    >>> _display_result("sample", "Sampled", None)
321
    False
322
323
    >>> _display_result("sample", "Sampled", 0)
324
    False
325
326
    >>> _display_result("sample", "Sampled", 0, allow_zero=True)
327
    True
328
329
    """
330 1
    if count is None:
331 1
        log.warning("No dependencies to %s", present)
332 1
    elif count == 1:
333 1
        log.info("%s 1 dependency", past)
334
    else:
335 1
        log.info("%s %s dependencies", past, count)
336
337 1
    if count:
338 1
        return True
339 1
    elif count is None:
340 1
        return False
341
    else:
342 1
        assert count == 0
343
        return allow_zero
344