diff options
| -rwxr-xr-x | repo | 50 | ||||
| -rw-r--r-- | subcmds/init.py | 11 | ||||
| -rw-r--r-- | tests/test_wrapper.py | 156 |
3 files changed, 195 insertions, 22 deletions
| @@ -463,6 +463,34 @@ class CloneFailure(Exception): | |||
| 463 | """ | 463 | """ |
| 464 | 464 | ||
| 465 | 465 | ||
| 466 | def check_repo_verify(repo_verify, quiet=False): | ||
| 467 | """Check the --repo-verify state.""" | ||
| 468 | if not repo_verify: | ||
| 469 | print('repo: warning: verification of repo code has been disabled;\n' | ||
| 470 | 'repo will not be able to verify the integrity of itself.\n', | ||
| 471 | file=sys.stderr) | ||
| 472 | return False | ||
| 473 | |||
| 474 | if NeedSetupGnuPG(): | ||
| 475 | return SetupGnuPG(quiet) | ||
| 476 | |||
| 477 | return True | ||
| 478 | |||
| 479 | |||
| 480 | def check_repo_rev(dst, rev, repo_verify=True, quiet=False): | ||
| 481 | """Check that |rev| is valid.""" | ||
| 482 | do_verify = check_repo_verify(repo_verify, quiet=quiet) | ||
| 483 | remote_ref, local_rev = resolve_repo_rev(dst, rev) | ||
| 484 | if not quiet and not remote_ref.startswith('refs/heads/'): | ||
| 485 | print('warning: repo is not tracking a remote branch, so it will not ' | ||
| 486 | 'receive updates', file=sys.stderr) | ||
| 487 | if do_verify: | ||
| 488 | rev = verify_rev(dst, remote_ref, local_rev, quiet) | ||
| 489 | else: | ||
| 490 | rev = local_rev | ||
| 491 | return (remote_ref, rev) | ||
| 492 | |||
| 493 | |||
| 466 | def _Init(args, gitc_init=False): | 494 | def _Init(args, gitc_init=False): |
| 467 | """Installs repo by cloning it over the network. | 495 | """Installs repo by cloning it over the network. |
| 468 | """ | 496 | """ |
| @@ -510,30 +538,12 @@ def _Init(args, gitc_init=False): | |||
| 510 | 538 | ||
| 511 | _CheckGitVersion() | 539 | _CheckGitVersion() |
| 512 | try: | 540 | try: |
| 513 | if not opt.repo_verify: | ||
| 514 | do_verify = False | ||
| 515 | print('repo: warning: verification of repo code has been disabled;\n' | ||
| 516 | 'repo will not be able to verify the integrity of itself.\n', | ||
| 517 | file=sys.stderr) | ||
| 518 | else: | ||
| 519 | if NeedSetupGnuPG(): | ||
| 520 | do_verify = SetupGnuPG(opt.quiet) | ||
| 521 | else: | ||
| 522 | do_verify = True | ||
| 523 | |||
| 524 | if not opt.quiet: | 541 | if not opt.quiet: |
| 525 | print('Downloading Repo source from', url) | 542 | print('Downloading Repo source from', url) |
| 526 | dst = os.path.abspath(os.path.join(repodir, S_repo)) | 543 | dst = os.path.abspath(os.path.join(repodir, S_repo)) |
| 527 | _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose) | 544 | _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose) |
| 528 | 545 | ||
| 529 | remote_ref, local_rev = resolve_repo_rev(dst, rev) | 546 | remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet) |
| 530 | if not opt.quiet and not remote_ref.startswith('refs/heads/'): | ||
| 531 | print('warning: repo is not tracking a remote branch, so it will not ' | ||
| 532 | 'receive updates', file=sys.stderr) | ||
| 533 | if do_verify: | ||
| 534 | rev = _Verify(dst, remote_ref, local_rev, opt.quiet) | ||
| 535 | else: | ||
| 536 | rev = local_rev | ||
| 537 | _Checkout(dst, remote_ref, rev, opt.quiet) | 547 | _Checkout(dst, remote_ref, rev, opt.quiet) |
| 538 | 548 | ||
| 539 | if not os.path.isfile(os.path.join(dst, 'repo')): | 549 | if not os.path.isfile(os.path.join(dst, 'repo')): |
| @@ -907,7 +917,7 @@ def resolve_repo_rev(cwd, committish): | |||
| 907 | raise CloneFailure() | 917 | raise CloneFailure() |
| 908 | 918 | ||
| 909 | 919 | ||
| 910 | def _Verify(cwd, remote_ref, rev, quiet): | 920 | def verify_rev(cwd, remote_ref, rev, quiet): |
| 911 | """Verify the commit has been signed by a tag.""" | 921 | """Verify the commit has been signed by a tag.""" |
| 912 | ret = run_git('describe', rev, cwd=cwd) | 922 | ret = run_git('describe', rev, cwd=cwd) |
| 913 | cur = ret.stdout.strip() | 923 | cur = ret.stdout.strip() |
diff --git a/subcmds/init.py b/subcmds/init.py index 431165d4..ce8b0187 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
| @@ -38,6 +38,7 @@ from project import SyncBuffer | |||
| 38 | from git_config import GitConfig | 38 | from git_config import GitConfig |
| 39 | from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD | 39 | from git_command import git_require, MIN_GIT_VERSION_SOFT, MIN_GIT_VERSION_HARD |
| 40 | import platform_utils | 40 | import platform_utils |
| 41 | from wrapper import Wrapper | ||
| 41 | 42 | ||
| 42 | 43 | ||
| 43 | class Init(InteractiveCommand, MirrorSafeCommand): | 44 | class Init(InteractiveCommand, MirrorSafeCommand): |
| @@ -499,6 +500,16 @@ to update the working directory files. | |||
| 499 | remote.url = opt.repo_url | 500 | remote.url = opt.repo_url |
| 500 | remote.Save() | 501 | remote.Save() |
| 501 | 502 | ||
| 503 | # Handle new --repo-rev requests. | ||
| 504 | if opt.repo_rev: | ||
| 505 | wrapper = Wrapper() | ||
| 506 | remote_ref, rev = wrapper.check_repo_rev( | ||
| 507 | rp.gitdir, opt.repo_rev, repo_verify=opt.repo_verify, quiet=opt.quiet) | ||
| 508 | branch = rp.GetBranch('default') | ||
| 509 | branch.merge = remote_ref | ||
| 510 | rp.work_git.update_ref('refs/heads/default', rev) | ||
| 511 | branch.Save() | ||
| 512 | |||
| 502 | if opt.worktree: | 513 | if opt.worktree: |
| 503 | # Older versions of git supported worktree, but had dangerous gc bugs. | 514 | # Older versions of git supported worktree, but had dangerous gc bugs. |
| 504 | git_require((2, 15, 0), fail=True, msg='git gc worktree corruption') | 515 | git_require((2, 15, 0), fail=True, msg='git gc worktree corruption') |
diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index 73c62cc1..136f7f11 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py | |||
| @@ -18,12 +18,14 @@ | |||
| 18 | 18 | ||
| 19 | from __future__ import print_function | 19 | from __future__ import print_function |
| 20 | 20 | ||
| 21 | import contextlib | ||
| 21 | import os | 22 | import os |
| 22 | import re | 23 | import re |
| 23 | import shutil | 24 | import shutil |
| 24 | import tempfile | 25 | import tempfile |
| 25 | import unittest | 26 | import unittest |
| 26 | 27 | ||
| 28 | import platform_utils | ||
| 27 | from pyversion import is_python3 | 29 | from pyversion import is_python3 |
| 28 | import wrapper | 30 | import wrapper |
| 29 | 31 | ||
| @@ -36,6 +38,18 @@ else: | |||
| 36 | from StringIO import StringIO | 38 | from StringIO import StringIO |
| 37 | 39 | ||
| 38 | 40 | ||
| 41 | @contextlib.contextmanager | ||
| 42 | def TemporaryDirectory(): | ||
| 43 | """Create a new empty git checkout for testing.""" | ||
| 44 | # TODO(vapier): Convert this to tempfile.TemporaryDirectory once we drop | ||
| 45 | # Python 2 support entirely. | ||
| 46 | try: | ||
| 47 | tempdir = tempfile.mkdtemp(prefix='repo-tests') | ||
| 48 | yield tempdir | ||
| 49 | finally: | ||
| 50 | platform_utils.rmtree(tempdir) | ||
| 51 | |||
| 52 | |||
| 39 | def fixture(*paths): | 53 | def fixture(*paths): |
| 40 | """Return a path relative to tests/fixtures. | 54 | """Return a path relative to tests/fixtures. |
| 41 | """ | 55 | """ |
| @@ -243,8 +257,93 @@ class CheckGitVersion(RepoWrapperTestCase): | |||
| 243 | self.wrapper._CheckGitVersion() | 257 | self.wrapper._CheckGitVersion() |
| 244 | 258 | ||
| 245 | 259 | ||
| 246 | class ResolveRepoRev(RepoWrapperTestCase): | 260 | class NeedSetupGnuPG(RepoWrapperTestCase): |
| 247 | """Check resolve_repo_rev behavior.""" | 261 | """Check NeedSetupGnuPG behavior.""" |
| 262 | |||
| 263 | def test_missing_dir(self): | ||
| 264 | """The ~/.repoconfig tree doesn't exist yet.""" | ||
| 265 | with TemporaryDirectory() as tempdir: | ||
| 266 | self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo') | ||
| 267 | self.assertTrue(self.wrapper.NeedSetupGnuPG()) | ||
| 268 | |||
| 269 | def test_missing_keyring(self): | ||
| 270 | """The keyring-version file doesn't exist yet.""" | ||
| 271 | with TemporaryDirectory() as tempdir: | ||
| 272 | self.wrapper.home_dot_repo = tempdir | ||
| 273 | self.assertTrue(self.wrapper.NeedSetupGnuPG()) | ||
| 274 | |||
| 275 | def test_empty_keyring(self): | ||
| 276 | """The keyring-version file exists, but is empty.""" | ||
| 277 | with TemporaryDirectory() as tempdir: | ||
| 278 | self.wrapper.home_dot_repo = tempdir | ||
| 279 | with open(os.path.join(tempdir, 'keyring-version'), 'w'): | ||
| 280 | pass | ||
| 281 | self.assertTrue(self.wrapper.NeedSetupGnuPG()) | ||
| 282 | |||
| 283 | def test_old_keyring(self): | ||
| 284 | """The keyring-version file exists, but it's old.""" | ||
| 285 | with TemporaryDirectory() as tempdir: | ||
| 286 | self.wrapper.home_dot_repo = tempdir | ||
| 287 | with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: | ||
| 288 | fp.write('1.0\n') | ||
| 289 | self.assertTrue(self.wrapper.NeedSetupGnuPG()) | ||
| 290 | |||
| 291 | def test_new_keyring(self): | ||
| 292 | """The keyring-version file exists, and is up-to-date.""" | ||
| 293 | with TemporaryDirectory() as tempdir: | ||
| 294 | self.wrapper.home_dot_repo = tempdir | ||
| 295 | with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: | ||
| 296 | fp.write('1000.0\n') | ||
| 297 | self.assertFalse(self.wrapper.NeedSetupGnuPG()) | ||
| 298 | |||
| 299 | |||
| 300 | class SetupGnuPG(RepoWrapperTestCase): | ||
| 301 | """Check SetupGnuPG behavior.""" | ||
| 302 | |||
| 303 | def test_full(self): | ||
| 304 | """Make sure it works completely.""" | ||
| 305 | with TemporaryDirectory() as tempdir: | ||
| 306 | self.wrapper.home_dot_repo = tempdir | ||
| 307 | self.assertTrue(self.wrapper.SetupGnuPG(True)) | ||
| 308 | with open(os.path.join(tempdir, 'keyring-version'), 'r') as fp: | ||
| 309 | data = fp.read() | ||
| 310 | self.assertEqual('.'.join(str(x) for x in self.wrapper.KEYRING_VERSION), | ||
| 311 | data.strip()) | ||
| 312 | |||
| 313 | |||
| 314 | class VerifyRev(RepoWrapperTestCase): | ||
| 315 | """Check verify_rev behavior.""" | ||
| 316 | |||
| 317 | def test_verify_passes(self): | ||
| 318 | """Check when we have a valid signed tag.""" | ||
| 319 | desc_result = self.wrapper.RunResult(0, 'v1.0\n', '') | ||
| 320 | gpg_result = self.wrapper.RunResult(0, '', '') | ||
| 321 | with mock.patch.object(self.wrapper, 'run_git', | ||
| 322 | side_effect=(desc_result, gpg_result)): | ||
| 323 | ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) | ||
| 324 | self.assertEqual('v1.0^0', ret) | ||
| 325 | |||
| 326 | def test_unsigned_commit(self): | ||
| 327 | """Check we fall back to signed tag when we have an unsigned commit.""" | ||
| 328 | desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '') | ||
| 329 | gpg_result = self.wrapper.RunResult(0, '', '') | ||
| 330 | with mock.patch.object(self.wrapper, 'run_git', | ||
| 331 | side_effect=(desc_result, gpg_result)): | ||
| 332 | ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) | ||
| 333 | self.assertEqual('v1.0^0', ret) | ||
| 334 | |||
| 335 | def test_verify_fails(self): | ||
| 336 | """Check we fall back to signed tag when we have an unsigned commit.""" | ||
| 337 | desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '') | ||
| 338 | gpg_result = Exception | ||
| 339 | with mock.patch.object(self.wrapper, 'run_git', | ||
| 340 | side_effect=(desc_result, gpg_result)): | ||
| 341 | with self.assertRaises(Exception): | ||
| 342 | self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) | ||
| 343 | |||
| 344 | |||
| 345 | class GitCheckoutTestCase(RepoWrapperTestCase): | ||
| 346 | """Tests that use a real/small git checkout.""" | ||
| 248 | 347 | ||
| 249 | GIT_DIR = None | 348 | GIT_DIR = None |
| 250 | REV_LIST = None | 349 | REV_LIST = None |
| @@ -274,6 +373,10 @@ class ResolveRepoRev(RepoWrapperTestCase): | |||
| 274 | 373 | ||
| 275 | shutil.rmtree(cls.GIT_DIR) | 374 | shutil.rmtree(cls.GIT_DIR) |
| 276 | 375 | ||
| 376 | |||
| 377 | class ResolveRepoRev(GitCheckoutTestCase): | ||
| 378 | """Check resolve_repo_rev behavior.""" | ||
| 379 | |||
| 277 | def test_explicit_branch(self): | 380 | def test_explicit_branch(self): |
| 278 | """Check refs/heads/branch argument.""" | 381 | """Check refs/heads/branch argument.""" |
| 279 | rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/stable') | 382 | rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/stable') |
| @@ -328,5 +431,54 @@ class ResolveRepoRev(RepoWrapperTestCase): | |||
| 328 | self.wrapper.resolve_repo_rev(self.GIT_DIR, 'boooooooya') | 431 | self.wrapper.resolve_repo_rev(self.GIT_DIR, 'boooooooya') |
| 329 | 432 | ||
| 330 | 433 | ||
| 434 | class CheckRepoVerify(RepoWrapperTestCase): | ||
| 435 | """Check check_repo_verify behavior.""" | ||
| 436 | |||
| 437 | def test_no_verify(self): | ||
| 438 | """Always fail with --no-repo-verify.""" | ||
| 439 | self.assertFalse(self.wrapper.check_repo_verify(False)) | ||
| 440 | |||
| 441 | def test_gpg_initialized(self): | ||
| 442 | """Should pass if gpg is setup already.""" | ||
| 443 | with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=False): | ||
| 444 | self.assertTrue(self.wrapper.check_repo_verify(True)) | ||
| 445 | |||
| 446 | def test_need_gpg_setup(self): | ||
| 447 | """Should pass/fail based on gpg setup.""" | ||
| 448 | with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=True): | ||
| 449 | with mock.patch.object(self.wrapper, 'SetupGnuPG') as m: | ||
| 450 | m.return_value = True | ||
| 451 | self.assertTrue(self.wrapper.check_repo_verify(True)) | ||
| 452 | |||
| 453 | m.return_value = False | ||
| 454 | self.assertFalse(self.wrapper.check_repo_verify(True)) | ||
| 455 | |||
| 456 | |||
| 457 | class CheckRepoRev(GitCheckoutTestCase): | ||
| 458 | """Check check_repo_rev behavior.""" | ||
| 459 | |||
| 460 | def test_verify_works(self): | ||
| 461 | """Should pass when verification passes.""" | ||
| 462 | with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True): | ||
| 463 | with mock.patch.object(self.wrapper, 'verify_rev', return_value='12345'): | ||
| 464 | rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable') | ||
| 465 | self.assertEqual('refs/heads/stable', rrev) | ||
| 466 | self.assertEqual('12345', lrev) | ||
| 467 | |||
| 468 | def test_verify_fails(self): | ||
| 469 | """Should fail when verification fails.""" | ||
| 470 | with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True): | ||
| 471 | with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception): | ||
| 472 | with self.assertRaises(Exception): | ||
| 473 | self.wrapper.check_repo_rev(self.GIT_DIR, 'stable') | ||
| 474 | |||
| 475 | def test_verify_ignore(self): | ||
| 476 | """Should pass when verification is disabled.""" | ||
| 477 | with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception): | ||
| 478 | rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable', repo_verify=False) | ||
| 479 | self.assertEqual('refs/heads/stable', rrev) | ||
| 480 | self.assertEqual(self.REV_LIST[1], lrev) | ||
| 481 | |||
| 482 | |||
| 331 | if __name__ == '__main__': | 483 | if __name__ == '__main__': |
| 332 | unittest.main() | 484 | unittest.main() |
