Passed
Push — develop ( 4d6495...2dddb7 )
by Jace
03:19
created

gitman.commands   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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