Merge lp:~acsone-openerp/anybox.recipe.openerp/git-merges into lp:anybox.recipe.openerp
- git-merges
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 533 | ||||
Proposed branch: | lp:~acsone-openerp/anybox.recipe.openerp/git-merges | ||||
Merge into: | lp:anybox.recipe.openerp | ||||
Diff against target: |
370 lines (+161/-117) 5 files modified
anybox/recipe/openerp/base.py (+4/-2) anybox/recipe/openerp/vcs/base.py (+3/-0) anybox/recipe/openerp/vcs/bzr.py (+3/-0) anybox/recipe/openerp/vcs/git.py (+68/-113) anybox/recipe/openerp/vcs/tests/test_git.py (+83/-2) |
||||
To merge this branch: | bzr merge lp:~acsone-openerp/anybox.recipe.openerp/git-merges | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Georges Racinet | Approve | ||
Raphaël Valyi - http://www.akretion.com (community) | Approve | ||
Laurent Mignon (Acsone) (community) | code, tested | Approve | |
Stefan Rijnhart (Opener) | Pending | ||
Review via email: mp+222608@code.launchpad.net |
Commit message
Description of the change
git merges and vcs-revert support
Laurent Mignon (Acsone) (lmi) wrote : | # |
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote : | # |
LGTM as long as vcs-merge wouldn't be used to update a local clone of OCB if OCB goes for continuous git-rebase.
NOTE: a further improvement to come after this merge to support shallow clones:
https:/
Georges Racinet (gracinet) wrote : | # |
So… this is a major refactor of the Git subsystem, together with the addition of the merge feature.
On the formal side, there is not a single existing test that had to be adapted (including the switch-branch ones).
It also leverages local idioms such that utils.check_output, or the function to issue a git commit (keeps proper insulation with other tests, works on dumb buildslaves…)
I believe the end result is indeed clearer, especially the relation with remote branches.
The only caveat is that test_git has some pep8 errors (the kind I'll fix in a second).
So, a big thank you, let's test this in the trunk !
Preview Diff
1 | === modified file 'anybox/recipe/openerp/base.py' | |||
2 | --- anybox/recipe/openerp/base.py 2014-06-08 21:55:32 +0000 | |||
3 | +++ anybox/recipe/openerp/base.py 2014-06-10 09:23:53 +0000 | |||
4 | @@ -494,12 +494,14 @@ | |||
5 | 494 | if not split: | 494 | if not split: |
6 | 495 | return | 495 | return |
7 | 496 | loc_type = split[0] | 496 | loc_type = split[0] |
10 | 497 | if loc_type != 'bzr': | 497 | if not loc_type in ('bzr', 'git'): |
11 | 498 | raise UserError("Only merges of type 'bzr' are " | 498 | raise UserError("Only merges of type 'bzr' and 'git' are " |
12 | 499 | "currently supported.") | 499 | "currently supported.") |
13 | 500 | options = dict(opt.split('=') for opt in split[4:]) | 500 | options = dict(opt.split('=') for opt in split[4:]) |
14 | 501 | if loc_type == 'bzr': | 501 | if loc_type == 'bzr': |
15 | 502 | options['bzr-init'] = 'merge' | 502 | options['bzr-init'] = 'merge' |
16 | 503 | else: | ||
17 | 504 | options['merge'] = True | ||
18 | 503 | 505 | ||
19 | 504 | repo_url, local_dir, repo_rev = split[1:4] | 506 | repo_url, local_dir, repo_rev = split[1:4] |
20 | 505 | location_spec = (repo_url, repo_rev) | 507 | location_spec = (repo_url, repo_rev) |
21 | 506 | 508 | ||
22 | === modified file 'anybox/recipe/openerp/vcs/base.py' | |||
23 | --- anybox/recipe/openerp/vcs/base.py 2014-06-08 12:37:29 +0000 | |||
24 | +++ anybox/recipe/openerp/vcs/base.py 2014-06-10 09:23:53 +0000 | |||
25 | @@ -176,3 +176,6 @@ | |||
26 | 176 | on the VCS type. | 176 | on the VCS type. |
27 | 177 | """ | 177 | """ |
28 | 178 | raise NotImplementedError | 178 | raise NotImplementedError |
29 | 179 | |||
30 | 180 | def archive(self, target_path): | ||
31 | 181 | raise NotImplementedError | ||
32 | 179 | 182 | ||
33 | === modified file 'anybox/recipe/openerp/vcs/bzr.py' | |||
34 | --- anybox/recipe/openerp/vcs/bzr.py 2014-06-08 12:37:29 +0000 | |||
35 | +++ anybox/recipe/openerp/vcs/bzr.py 2014-06-10 09:23:53 +0000 | |||
36 | @@ -255,6 +255,9 @@ | |||
37 | 255 | If target_dir already exists, does a simple pull. | 255 | If target_dir already exists, does a simple pull. |
38 | 256 | Offline-mode: no branch nor pull, but update. | 256 | Offline-mode: no branch nor pull, but update. |
39 | 257 | In all cases, an attempt to update is performed before any pull | 257 | In all cases, an attempt to update is performed before any pull |
40 | 258 | |||
41 | 259 | Special case: if the 'merge' option is True, | ||
42 | 260 | merge revision into current branch. | ||
43 | 258 | """ | 261 | """ |
44 | 259 | target_dir = self.target_dir | 262 | target_dir = self.target_dir |
45 | 260 | offline = self.offline | 263 | offline = self.offline |
46 | 261 | 264 | ||
47 | === modified file 'anybox/recipe/openerp/vcs/git.py' | |||
48 | --- anybox/recipe/openerp/vcs/git.py 2014-06-08 12:37:29 +0000 | |||
49 | +++ anybox/recipe/openerp/vcs/git.py 2014-06-10 09:23:53 +0000 | |||
50 | @@ -1,5 +1,4 @@ | |||
51 | 1 | import os | 1 | import os |
52 | 2 | import sys | ||
53 | 3 | import subprocess | 2 | import subprocess |
54 | 4 | import logging | 3 | import logging |
55 | 5 | import tempfile | 4 | import tempfile |
56 | @@ -8,13 +7,14 @@ | |||
57 | 8 | from .base import BaseRepo | 7 | from .base import BaseRepo |
58 | 9 | from .base import SUBPROCESS_ENV | 8 | from .base import SUBPROCESS_ENV |
59 | 10 | from anybox.recipe.openerp import utils | 9 | from anybox.recipe.openerp import utils |
60 | 11 | import re | ||
61 | 12 | 10 | ||
62 | 13 | logger = logging.getLogger(__name__) | 11 | logger = logging.getLogger(__name__) |
63 | 14 | 12 | ||
64 | 13 | BUILDOUT_ORIGIN = 'origin' | ||
65 | 14 | |||
66 | 15 | 15 | ||
67 | 16 | class GitRepo(BaseRepo): | 16 | class GitRepo(BaseRepo): |
69 | 17 | """Represent a Git clone tied to a reference branch.""" | 17 | """Represent a Git clone tied to a reference branch/commit/tag.""" |
70 | 18 | 18 | ||
71 | 19 | vcs_control_dir = '.git' | 19 | vcs_control_dir = '.git' |
72 | 20 | 20 | ||
73 | @@ -46,50 +46,69 @@ | |||
74 | 46 | return bool(out.strip()) | 46 | return bool(out.strip()) |
75 | 47 | 47 | ||
76 | 48 | def get_update(self, revision): | 48 | def get_update(self, revision): |
78 | 49 | """Ensure that target_dir is a branch of url at specified revision. | 49 | """Make it so that the target directory is at the prescribed revision. |
79 | 50 | 50 | ||
82 | 51 | If target_dir already exists, does a simple fetch. | 51 | Special case: if the 'merge' option is True, |
83 | 52 | Offline-mode: no branch nor fetch, but checkout. | 52 | merge revision into current branch. |
84 | 53 | """ | 53 | """ |
85 | 54 | if self.options.get('merge'): | ||
86 | 55 | return self.merge(revision) | ||
87 | 56 | |||
88 | 54 | target_dir = self.target_dir | 57 | target_dir = self.target_dir |
89 | 55 | url = self.url | 58 | url = self.url |
90 | 56 | offline = self.offline | 59 | offline = self.offline |
91 | 57 | rev_str = revision | ||
92 | 58 | 60 | ||
93 | 59 | with working_directory_keeper: | 61 | with working_directory_keeper: |
97 | 60 | is_target_dir_exists = os.path.exists(target_dir) | 62 | if not os.path.exists(target_dir): |
95 | 61 | if not is_target_dir_exists: | ||
96 | 62 | # TODO case of local url ? | ||
98 | 63 | if offline: | 63 | if offline: |
99 | 64 | # TODO case of local url ? | ||
100 | 64 | raise IOError( | 65 | raise IOError( |
110 | 65 | "git repository %s does not exist; cannot clone it " | 66 | "git repository %s does not exist; cannot clone " |
111 | 66 | "from %s (offline mode)" % (target_dir, url)) | 67 | "it from %s (offline mode)" % (target_dir, url)) |
112 | 67 | 68 | logger.info("%s> git init", target_dir) | |
113 | 68 | os.chdir(os.path.split(target_dir)[0]) | 69 | subprocess.check_call(['git', 'init', target_dir]) |
114 | 69 | logger.info("Cloning %s ...", url) | 70 | os.chdir(target_dir) |
115 | 70 | subprocess.check_call(['git', 'clone', url, target_dir]) | 71 | logger.info("%s> git remote add %s %s", |
116 | 71 | os.chdir(target_dir) | 72 | target_dir, BUILDOUT_ORIGIN, url) |
117 | 72 | 73 | subprocess.check_call(['git', 'remote', 'add', | |
118 | 73 | if is_target_dir_exists: | 74 | BUILDOUT_ORIGIN, url]) |
119 | 75 | |||
120 | 76 | if not offline: | ||
121 | 74 | # TODO what if remote repo is actually local fs ? | 77 | # TODO what if remote repo is actually local fs ? |
138 | 75 | if not offline: | 78 | os.chdir(target_dir) |
139 | 76 | logger.info("Fetch for git repo %s (rev %s)...", | 79 | logger.info("%s> git remote set-url %s %s", |
140 | 77 | target_dir, rev_str) | 80 | target_dir, BUILDOUT_ORIGIN, url) |
141 | 78 | subprocess.check_call(['git', 'fetch']) | 81 | subprocess.call(['git', 'remote', 'set-url', |
142 | 79 | 82 | BUILDOUT_ORIGIN, url]) | |
143 | 80 | if revision and self._needToSwitchRevision(revision): | 83 | logger.info("%s> git fetch %s", |
144 | 81 | # switch to the expected revision | 84 | target_dir, BUILDOUT_ORIGIN) |
145 | 82 | logger.info("Checkout %s to revision %s", | 85 | subprocess.check_call(['git', 'fetch', BUILDOUT_ORIGIN]) |
146 | 83 | target_dir, revision) | 86 | # TODO: check what happens when there are local changes |
147 | 84 | self._switch(revision) | 87 | # TODO: what about the 'clean' option |
148 | 85 | 88 | logger.info("%s> git checkout %s", target_dir, revision) | |
149 | 86 | if self._isATrackedBranch(revision): | 89 | subprocess.check_call(['git', 'checkout', revision]) |
150 | 87 | if not offline: | 90 | if self._is_a_branch(revision): |
151 | 88 | logger.info("Pull for git repo %s (rev %s)...", | 91 | # fast forward |
152 | 89 | target_dir, rev_str) | 92 | logger.info("%s> git merge merge %s/%s", |
153 | 90 | subprocess.check_call(['git', 'pull']) | 93 | target_dir, BUILDOUT_ORIGIN, revision) |
154 | 94 | subprocess.check_call(['git', 'merge', | ||
155 | 95 | BUILDOUT_ORIGIN + '/' + revision]) | ||
156 | 96 | |||
157 | 97 | def merge(self, revision): | ||
158 | 98 | """Merge revision into current branch""" | ||
159 | 99 | with working_directory_keeper: | ||
160 | 100 | if not self.is_versioned(self.target_dir): | ||
161 | 101 | raise RuntimeError("Cannot merge into non existent " | ||
162 | 102 | "or non git local directory %s" % | ||
163 | 103 | self.target_dir) | ||
164 | 104 | os.chdir(self.target_dir) | ||
165 | 105 | logger.info("%s> git pull %s %s", | ||
166 | 106 | self.target_dir, self.url, revision) | ||
167 | 107 | subprocess.check_call(['git', 'pull', '--no-edit', | ||
168 | 108 | self.url, revision]) | ||
169 | 91 | 109 | ||
170 | 92 | def archive(self, target_path): | 110 | def archive(self, target_path): |
171 | 111 | # TODO: does this work with merge-ins? | ||
172 | 93 | revision = self.parents()[0] | 112 | revision = self.parents()[0] |
173 | 94 | if not os.path.exists(target_path): | 113 | if not os.path.exists(target_path): |
174 | 95 | os.makedirs(target_path) | 114 | os.makedirs(target_path) |
175 | @@ -104,81 +123,17 @@ | |||
176 | 104 | '-C', target_path]) | 123 | '-C', target_path]) |
177 | 105 | os.unlink(target_tar.name) | 124 | os.unlink(target_tar.name) |
178 | 106 | 125 | ||
257 | 107 | def _isATrackedBranch(self, revision): | 126 | def revert(self, revision): |
258 | 108 | rbp = self._remote_branch_prefix | 127 | with working_directory_keeper: |
259 | 109 | branches = utils.check_output(["git", "branch", "-a"]) | 128 | os.chdir(self.target_dir) |
260 | 110 | branch = revision | 129 | subprocess.check_call(['git', 'checkout', revision]) |
261 | 111 | return re.search( | 130 | if self._is_a_branch(revision): |
262 | 112 | "^ " + re.escape(rbp) + "\/" + re.escape(branch) + "$", branches, | 131 | subprocess.check_call(['git', 'reset', '--hard', |
263 | 113 | re.M) | 132 | BUILDOUT_ORIGIN + '/' + revision]) |
264 | 114 | 133 | else: | |
265 | 115 | def _needToSwitchRevision(self, revision): | 134 | subprocess.check_call(['git', 'reset', '--hard', revision]) |
266 | 116 | """ Check if we need to checkout to an other branch | 135 | |
267 | 117 | """ | 136 | def _is_a_branch(self, revision): |
268 | 118 | p = utils.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) | 137 | branches = utils.check_output(["git", "branch"]) |
269 | 119 | rev = p.split()[0] # remove \n | 138 | branches = branches.split() |
270 | 120 | logger.info("Current revision '%s' - Expected revision '%s'", | 139 | return revision in branches |
193 | 121 | rev, revision) | ||
194 | 122 | return rev != revision | ||
195 | 123 | |||
196 | 124 | def _switch(self, revision): | ||
197 | 125 | rbp = self._remote_branch_prefix | ||
198 | 126 | branches = utils.check_output(["git", "branch", "-a"]) | ||
199 | 127 | branch = revision | ||
200 | 128 | if re.search("^(\*| ) %s$" % re.escape(branch), branches, re.M): | ||
201 | 129 | # the branch is local, normal checkout will work | ||
202 | 130 | logger.info("The branch is local; normal checkout ") | ||
203 | 131 | argv = ["checkout", branch] | ||
204 | 132 | elif re.search( | ||
205 | 133 | "^ " + re.escape(rbp) + "\/" + re.escape(branch) + "$", branches, | ||
206 | 134 | re.M): | ||
207 | 135 | # the branch is not local, normal checkout won't work here | ||
208 | 136 | logger.info("The branch is not local; checkout remote branch ") | ||
209 | 137 | argv = ["checkout", "-b", branch, "%s/%s" % (rbp, branch)] | ||
210 | 138 | else: | ||
211 | 139 | # A tag or revision was specified instead of a branch | ||
212 | 140 | logger.info("Checkout tag or revision") | ||
213 | 141 | argv = ["checkout", revision] | ||
214 | 142 | # runs the checkout with predetermined arguments | ||
215 | 143 | argv.insert(0, "git") | ||
216 | 144 | subprocess.check_call(argv) | ||
217 | 145 | |||
218 | 146 | @property | ||
219 | 147 | def _remote_branch_prefix(self): | ||
220 | 148 | version = self._git_version | ||
221 | 149 | if version < (1, 6, 3): | ||
222 | 150 | return "origin" | ||
223 | 151 | else: | ||
224 | 152 | return "remotes/origin" | ||
225 | 153 | |||
226 | 154 | @property | ||
227 | 155 | def _git_version(self): | ||
228 | 156 | out = utils.check_output(["git", "--version"]) | ||
229 | 157 | m = re.search("git version (\d+)\.(\d+)(\.\d+)?(\.\d+)?", out) | ||
230 | 158 | if m is None: | ||
231 | 159 | logger.error("Unable to parse git version output") | ||
232 | 160 | logger.error("'git --version' output was:\n%s\n%s", out) | ||
233 | 161 | sys.exit(1) | ||
234 | 162 | version = m.groups() | ||
235 | 163 | |||
236 | 164 | if version[3] is not None: | ||
237 | 165 | version = ( | ||
238 | 166 | int(version[0]), | ||
239 | 167 | int(version[1]), | ||
240 | 168 | int(version[2][1:]), | ||
241 | 169 | int(version[3][1:]) | ||
242 | 170 | ) | ||
243 | 171 | elif version[2] is not None: | ||
244 | 172 | version = ( | ||
245 | 173 | int(version[0]), | ||
246 | 174 | int(version[1]), | ||
247 | 175 | int(version[2][1:]) | ||
248 | 176 | ) | ||
249 | 177 | else: | ||
250 | 178 | version = (int(version[0]), int(version[1])) | ||
251 | 179 | if version < (1, 5): | ||
252 | 180 | logger.error( | ||
253 | 181 | "Git version %s is unsupported, please upgrade", | ||
254 | 182 | ".".join([str(v) for v in version])) | ||
255 | 183 | sys.exit(1) | ||
256 | 184 | return version | ||
271 | 185 | 140 | ||
272 | === modified file 'anybox/recipe/openerp/vcs/tests/test_git.py' | |||
273 | --- anybox/recipe/openerp/vcs/tests/test_git.py 2014-06-08 12:37:29 +0000 | |||
274 | +++ anybox/recipe/openerp/vcs/tests/test_git.py 2014-06-10 09:23:53 +0000 | |||
275 | @@ -36,8 +36,8 @@ | |||
276 | 36 | 36 | ||
277 | 37 | def create_src(self): | 37 | def create_src(self): |
278 | 38 | os.chdir(self.src_dir) | 38 | os.chdir(self.src_dir) |
281 | 39 | subprocess.call(['git', 'init', 'src-branch']) | 39 | subprocess.call(['git', 'init', 'src-repo']) |
282 | 40 | self.src_repo = os.path.join(self.src_dir, 'src-branch') | 40 | self.src_repo = os.path.join(self.src_dir, 'src-repo') |
283 | 41 | self.commit_1_sha = git_write_commit(self.src_repo, 'tracked', | 41 | self.commit_1_sha = git_write_commit(self.src_repo, 'tracked', |
284 | 42 | "first", msg="initial commit") | 42 | "first", msg="initial commit") |
285 | 43 | self.commit_2_sha = git_write_commit(self.src_repo, 'tracked', | 43 | self.commit_2_sha = git_write_commit(self.src_repo, 'tracked', |
286 | @@ -229,3 +229,84 @@ | |||
287 | 229 | branch("remotebranch") | 229 | branch("remotebranch") |
288 | 230 | with open(os.path.join(target_dir, 'tracked')) as f: | 230 | with open(os.path.join(target_dir, 'tracked')) as f: |
289 | 231 | self.assertEquals(f.read().strip(), "last after remote branch") | 231 | self.assertEquals(f.read().strip(), "last after remote branch") |
290 | 232 | |||
291 | 233 | |||
292 | 234 | class GitMergeTestCase(GitBaseTestCase): | ||
293 | 235 | |||
294 | 236 | def create_src(self): | ||
295 | 237 | GitBaseTestCase.create_src(self) | ||
296 | 238 | os.chdir(self.src_repo) | ||
297 | 239 | |||
298 | 240 | self.make_branch(self.src_repo, 'branch1') | ||
299 | 241 | self.checkout_branch(self.src_repo, 'branch1') | ||
300 | 242 | git_write_commit(self.src_repo, 'file_on_branch1', | ||
301 | 243 | "file on branch 1", msg="on branch 1") | ||
302 | 244 | self.checkout_branch(self.src_repo, 'master') | ||
303 | 245 | |||
304 | 246 | self.make_branch(self.src_repo, 'branch2') | ||
305 | 247 | self.checkout_branch(self.src_repo, 'branch2') | ||
306 | 248 | git_write_commit(self.src_repo, 'file_on_branch2', | ||
307 | 249 | "file on branch 2", msg="on branch 2") | ||
308 | 250 | self.checkout_branch(self.src_repo, 'master') | ||
309 | 251 | |||
310 | 252 | def make_branch(self, src_dir, name): | ||
311 | 253 | """create a branch | ||
312 | 254 | """ | ||
313 | 255 | subprocess.check_call(['git', 'branch', name], cwd=src_dir) | ||
314 | 256 | |||
315 | 257 | def checkout_branch(self, src_dir, name): | ||
316 | 258 | """checkout a branch | ||
317 | 259 | """ | ||
318 | 260 | subprocess.check_call(['git', 'checkout', name], cwd=src_dir) | ||
319 | 261 | |||
320 | 262 | def test_01_check_src_repo(self): | ||
321 | 263 | """test if the creation of source repo worked as expected""" | ||
322 | 264 | target_dir = os.path.join(self.dst_dir, "to_repo") | ||
323 | 265 | repo = GitRepo(target_dir, self.src_repo) | ||
324 | 266 | |||
325 | 267 | repo('master') | ||
326 | 268 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
327 | 269 | 'file_on_branch1 should not exist') | ||
328 | 270 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch2')), | ||
329 | 271 | 'file_on_branch2 should not exist') | ||
330 | 272 | |||
331 | 273 | repo('branch1') | ||
332 | 274 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
333 | 275 | 'file_on_branch1 should exist') | ||
334 | 276 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch2')), | ||
335 | 277 | 'file_on_branch2 should not exist') | ||
336 | 278 | |||
337 | 279 | repo('branch2') | ||
338 | 280 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
339 | 281 | 'file_on_branch1 should not exist') | ||
340 | 282 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch2')), | ||
341 | 283 | 'file_on_branch2 should exist') | ||
342 | 284 | |||
343 | 285 | def test_02_merge(self): | ||
344 | 286 | target_dir = os.path.join(self.dst_dir, "to_repo") | ||
345 | 287 | repo = GitRepo(target_dir, self.src_repo) | ||
346 | 288 | repo('master') | ||
347 | 289 | |||
348 | 290 | repo.merge('branch1') | ||
349 | 291 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
350 | 292 | 'file_on_branch1 should exist') | ||
351 | 293 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch2')), | ||
352 | 294 | 'file_on_branch2 should not exist') | ||
353 | 295 | repo.merge('branch2') | ||
354 | 296 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
355 | 297 | 'file_on_branch1 should exist') | ||
356 | 298 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch2')), | ||
357 | 299 | 'file_on_branch2 should exist') | ||
358 | 300 | |||
359 | 301 | def test_03_revert(self): | ||
360 | 302 | target_dir = os.path.join(self.dst_dir, "to_repo") | ||
361 | 303 | repo = GitRepo(target_dir, self.src_repo) | ||
362 | 304 | repo('master') | ||
363 | 305 | |||
364 | 306 | repo.merge('branch1') | ||
365 | 307 | self.assertTrue(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
366 | 308 | 'file_on_branch1 should exist') | ||
367 | 309 | |||
368 | 310 | repo.revert('master') | ||
369 | 311 | self.assertFalse(os.path.exists(os.path.join(target_dir, 'file_on_branch1')), | ||
370 | 312 | 'file_on_branch1 should not exist') |
Hi,
The code is really more natural and simple with more features covered!
LGTM,