Skip to content

Commit 2c12fef

Browse files
committed
Submodule: Added dry_run and progress parameter to the update method. It is copatible to the RemoteProgress and should satisfy all progress needs. Dryrun will be useful in conjunction with the progress to verify the changes to be done
1 parent cf1d5bd commit 2c12fef

File tree

5 files changed

+158
-135
lines changed

5 files changed

+158
-135
lines changed

doc/source/changes.rst

+2-6
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,11 @@ Changelog
2121

2222
* ``create(...)`` method now supports the reflog, but will not raise ``GitCommandError`` anymore as it is a pure python implementation now. Instead, it raises ``OSError``.
2323

24-
* **Intrusive Changes** to ``Actor`` type
25-
26-
* the *name* field is now using unicode if ascii does not match
27-
2824
* **Intrusive Changes** to ``Repo`` type
2925

30-
* ``create_head(...)`` method does not support **kwargs anymore, instead it supports a logmsg parameter
26+
* ``create_head(...)`` method does not support kwargs anymore, instead it supports a logmsg parameter
3127

32-
* Repo.rev_parse now supports the [ref]@{n} syntax, where n is the number of steps to look into the reference's past
28+
* Repo.rev_parse now supports the [ref]@{n} syntax, where *n* is the number of steps to look into the reference's past
3329

3430
* **BugFixes**
3531

objects/submodule/base.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
from git.util import (
1313
Iterable,
1414
join_path_native,
15-
to_native_path_linux
15+
to_native_path_linux,
16+
RemoteProgress
1617
)
1718

1819
from git.config import SectionConstraint
1920
from git.exc import (
2021
InvalidGitRepositoryError,
2122
NoSuchPathError
2223
)
24+
2325
import stat
2426
import git
2527

@@ -29,7 +31,16 @@
2931

3032
import shutil
3133

32-
__all__ = ["Submodule"]
34+
__all__ = ["Submodule", "UpdateProgress"]
35+
36+
37+
class UpdateProgress(RemoteProgress):
38+
"""Class providing detailed progress information to the caller who should
39+
derive from it and implement the ``update(...)`` message"""
40+
ADD, REMOVE, UPDATE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes+3)]
41+
42+
__slots__ = tuple()
43+
3344

3445

3546
# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import
@@ -285,7 +296,7 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
285296

286297
return sm
287298

288-
def update(self, recursive=False, init=True, to_latest_revision=False):
299+
def update(self, recursive=False, init=True, to_latest_revision=False, progress=None):
289300
"""Update the repository of this submodule to point to the checkout
290301
we point at with the binsha of this instance.
291302
@@ -297,13 +308,17 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
297308
This only works if we have a local tracking branch, which is the case
298309
if the remote repository had a master branch, or of the 'branch' option
299310
was specified for this submodule and the branch existed remotely
311+
:param progress: UpdateProgress instance or None of no progress should be shown
300312
:note: does nothing in bare repositories
301313
:note: method is definitely not atomic if recurisve is True
302314
:return: self"""
303315
if self.repo.bare:
304316
return self
305317
#END pass in bare mode
306318

319+
if progress is None:
320+
progress = UpdateProgress()
321+
#END handle progress
307322

308323
# ASSURE REPO IS PRESENT AND UPTODATE
309324
#####################################

objects/submodule/root.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from base import Submodule
1+
from base import Submodule, UpdateProgress
22
from util import (
33
find_first_remote_branch
44
)
@@ -9,7 +9,7 @@
99

1010
__all__ = ["RootModule"]
1111

12-
12+
1313
class RootModule(Submodule):
1414
"""A (virtual) Root of all submodules in the given repository. It can be used
1515
to more easily traverse all submodules of the master repository"""
@@ -38,7 +38,8 @@ def _clear_cache(self):
3838

3939
#{ Interface
4040

41-
def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, to_latest_revision=False):
41+
def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
42+
to_latest_revision=False, progress=None, dry_run=False):
4243
"""Update the submodules of this repository to the current HEAD commit.
4344
This method behaves smartly by determining changes of the path of a submodules
4445
repository, next to changes to the to-be-checked-out commit or the branch to be
@@ -57,11 +58,18 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
5758
:param init: If we encounter a new module which would need to be initialized, then do it.
5859
:param to_latest_revision: If True, instead of checking out the revision pointed to
5960
by this submodule's sha, the checked out tracking branch will be merged with the
60-
newest remote branch fetched from the repository's origin"""
61+
newest remote branch fetched from the repository's origin
62+
:param progress: UpdateProgress instance or None if no progress should be sent
63+
:param dry_run: if True, operations will not actually be performed. Progress messages
64+
will change accordingly to indicate the WOULD DO state of the operation."""
6165
if self.repo.bare:
6266
raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
6367
# END handle bare
6468

69+
if progress is None:
70+
progress = UpdateProgress()
71+
#END assure progress is set
72+
6573
repo = self.repo
6674

6775
# HANDLE COMMITS
@@ -125,7 +133,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
125133

126134
assert nn not in [r.name for r in rmts]
127135
smr = smm.create_remote(nn, sm.url)
128-
smr.fetch()
136+
smr.fetch(progress=progress)
129137

130138
# If we have a tracking branch, it should be available
131139
# in the new remote as well.
@@ -234,7 +242,7 @@ def update(self, previous_commit=None, recursive=True, force_remove=False, init=
234242
######################################
235243
for sm in sms:
236244
# update the submodule using the default method
237-
sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision)
245+
sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision, progress=progress)
238246

239247
# update recursively depth first - question is which inconsitent
240248
# state will be better in case it fails somewhere. Defective branch

remote.py

+2-119
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
# Module implementing a remote object allowing easy access to git remotes
88

99
from exc import GitCommandError
10-
from objects import Commit
1110
from ConfigParser import NoOptionError
1211
from config import SectionConstraint
1312

1413
from git.util import (
1514
LazyMixin,
1615
Iterable,
17-
IterableList
16+
IterableList,
17+
RemoteProgress
1818
)
1919

2020
from refs import (
@@ -33,123 +33,6 @@
3333

3434
__all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
3535

36-
class RemoteProgress(object):
37-
"""
38-
Handler providing an interface to parse progress information emitted by git-push
39-
and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
40-
"""
41-
BEGIN, END, COUNTING, COMPRESSING, WRITING = [ 1 << x for x in range(5) ]
42-
STAGE_MASK = BEGIN|END
43-
OP_MASK = COUNTING|COMPRESSING|WRITING
44-
45-
__slots__ = ("_cur_line", "_seen_ops")
46-
re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
47-
re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
48-
49-
def __init__(self):
50-
self._seen_ops = list()
51-
52-
def _parse_progress_line(self, line):
53-
"""Parse progress information from the given line as retrieved by git-push
54-
or git-fetch
55-
56-
:return: list(line, ...) list of lines that could not be processed"""
57-
# handle
58-
# Counting objects: 4, done.
59-
# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
60-
self._cur_line = line
61-
sub_lines = line.split('\r')
62-
failed_lines = list()
63-
for sline in sub_lines:
64-
# find esacpe characters and cut them away - regex will not work with
65-
# them as they are non-ascii. As git might expect a tty, it will send them
66-
last_valid_index = None
67-
for i,c in enumerate(reversed(sline)):
68-
if ord(c) < 32:
69-
# its a slice index
70-
last_valid_index = -i-1
71-
# END character was non-ascii
72-
# END for each character in sline
73-
if last_valid_index is not None:
74-
sline = sline[:last_valid_index]
75-
# END cut away invalid part
76-
sline = sline.rstrip()
77-
78-
cur_count, max_count = None, None
79-
match = self.re_op_relative.match(sline)
80-
if match is None:
81-
match = self.re_op_absolute.match(sline)
82-
83-
if not match:
84-
self.line_dropped(sline)
85-
failed_lines.append(sline)
86-
continue
87-
# END could not get match
88-
89-
op_code = 0
90-
remote, op_name, percent, cur_count, max_count, message = match.groups()
91-
92-
# get operation id
93-
if op_name == "Counting objects":
94-
op_code |= self.COUNTING
95-
elif op_name == "Compressing objects":
96-
op_code |= self.COMPRESSING
97-
elif op_name == "Writing objects":
98-
op_code |= self.WRITING
99-
else:
100-
raise ValueError("Operation name %r unknown" % op_name)
101-
102-
# figure out stage
103-
if op_code not in self._seen_ops:
104-
self._seen_ops.append(op_code)
105-
op_code |= self.BEGIN
106-
# END begin opcode
107-
108-
if message is None:
109-
message = ''
110-
# END message handling
111-
112-
message = message.strip()
113-
done_token = ', done.'
114-
if message.endswith(done_token):
115-
op_code |= self.END
116-
message = message[:-len(done_token)]
117-
# END end message handling
118-
119-
self.update(op_code, cur_count, max_count, message)
120-
# END for each sub line
121-
return failed_lines
122-
123-
def line_dropped(self, line):
124-
"""Called whenever a line could not be understood and was therefore dropped."""
125-
pass
126-
127-
def update(self, op_code, cur_count, max_count=None, message=''):
128-
"""Called whenever the progress changes
129-
130-
:param op_code:
131-
Integer allowing to be compared against Operation IDs and stage IDs.
132-
133-
Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation
134-
ID as well as END. It may be that BEGIN and END are set at once in case only
135-
one progress message was emitted due to the speed of the operation.
136-
Between BEGIN and END, none of these flags will be set
137-
138-
Operation IDs are all held within the OP_MASK. Only one Operation ID will
139-
be active per call.
140-
:param cur_count: Current absolute count of items
141-
142-
:param max_count:
143-
The maximum count of items we expect. It may be None in case there is
144-
no maximum number of items or if it is (yet) unknown.
145-
146-
:param message:
147-
In case of the 'WRITING' operation, it contains the amount of bytes
148-
transferred. It may possibly be used for other purposes as well.
149-
150-
You may read the contents of the current line in self._cur_line"""
151-
pass
152-
15336

15437
class PushInfo(object):
15538
"""

0 commit comments

Comments
 (0)