gitman.commands   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 193
dl 0
loc 368
ccs 138
cts 138
cp 1
rs 9.6
c 0
b 0
f 0
wmc 35

11 Functions

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