1#!/usr/bin/python2 2import logging, mox, os, shutil, tempfile, unittest, utils 3 4# This makes autotest_lib imports available. 5import common 6from autotest_lib.client.common_lib import revision_control 7 8 9class GitRepoManager(object): 10 """ 11 A wrapper for GitRepo. 12 """ 13 commit_hash = None 14 commit_msg = None 15 repodir = None 16 git_repo_manager = None 17 18 19 def __init__(self, main_repo=None): 20 """ 21 Setup self.git_repo_manager. 22 23 If a main_repo is present clone it. 24 Otherwise create a directory in /tmp and init it. 25 26 @param main_repo: GitRepo representing main. 27 """ 28 if main_repo is None: 29 self.repodir = tempfile.mktemp(suffix='main') 30 self._create_git_repo(self.repodir) 31 self.git_repo_manager = revision_control.GitRepo( 32 self.repodir, 33 self.repodir, 34 abs_work_tree=self.repodir) 35 self._setup_git_environment() 36 # Create an initial commit. We really care about the common case 37 # where there exists a commit in the upstream repo. 38 self._edit('initial_commit_file', 'is_non_empty') 39 self.add() 40 self.commit('initial_commit') 41 else: 42 self.repodir = tempfile.mktemp(suffix='dependent') 43 self.git_repo_manager = revision_control.GitRepo( 44 self.repodir, 45 main_repo.repodir, 46 abs_work_tree=self.repodir) 47 self.git_repo_manager.clone() 48 self._setup_git_environment() 49 50 51 def _setup_git_environment(self): 52 """ 53 Mock out basic git environment to keep tests deterministic. 54 """ 55 # Set user and email for the test git checkout. 56 self.git_repo_manager.gitcmd('config user.name Unittests') 57 self.git_repo_manager.gitcmd('config user.email utests@chromium.org') 58 59 60 def _edit(self, filename='foo', msg='bar'): 61 """ 62 Write msg into a file in the repodir. 63 64 @param filename: Name of the file in current repo. 65 If none exists one will be created. 66 @param msg: A message to write into the file. 67 """ 68 local_file_name = os.path.join(self.git_repo_manager.repodir, 69 filename) 70 with open(local_file_name, 'w') as f: 71 f.write(msg) 72 73 74 def _create_git_repo(self, repodir): 75 """ 76 Init a new git repository. 77 78 @param repodir: directory for repo. 79 """ 80 logging.info('initializing git repo in: %s', repodir) 81 gitcmd = 'git init %s' % repodir 82 rv = utils.run(gitcmd) 83 if rv.exit_status != 0: 84 logging.error(rv.stderr) 85 raise revision_control.revision_control.GitError(gitcmd + 'failed') 86 87 88 def add(self): 89 """ 90 Add all unadded files in repodir to repo. 91 """ 92 rv = self.git_repo_manager.gitcmd('add .') 93 if rv.exit_status != 0: 94 logging.error(rv.stderr) 95 raise revision_control.GitError('Unable to add files to repo', rv) 96 97 98 def commit(self, msg='default'): 99 """ 100 Commit changes to repo with the supplied commit msg. 101 Also updates commit_hash with the hash for this commit. 102 103 @param msg: A message that goes with the commit. 104 """ 105 self.git_repo_manager.commit(msg) 106 self.commit_hash = self.git_repo_manager.get_latest_commit_hash() 107 108 109 def get_main_tot(self): 110 """ 111 Get everything from mains TOT squashing local changes. 112 If the dependent repo is empty pull from main. 113 """ 114 # TODO b:169251326 terms below are set outside of this codebase 115 # and should be updated when possible. ("master" -> "main") 116 # Currently (but I believe it will eventually) does not support 117 # `reset --hard origin/main` (must be origin/master). 118 self.git_repo_manager.reinit_repo_at('master') 119 self.commit_hash = self.git_repo_manager.get_latest_commit_hash() 120 121 122class RevisionControlUnittest(mox.MoxTestBase): 123 """ 124 A unittest to exercise build_externals.py's usage 125 of revision_control.py's Git wrappers. 126 """ 127 main_repo=None 128 dependent_repo=None 129 130 def setUp(self): 131 """ 132 Create a main repo and clone it into a dependent repo. 133 """ 134 super(RevisionControlUnittest, self).setUp() 135 self.main_repo = GitRepoManager() 136 self.dependent_repo = GitRepoManager(self.main_repo) 137 138 139 def tearDown(self): 140 """ 141 Delete temporary directories. 142 """ 143 shutil.rmtree(self.main_repo.repodir) 144 shutil.rmtree(self.dependent_repo.repodir) 145 super(RevisionControlUnittest, self).tearDown() 146 147 148 def testCommit(self): 149 """ 150 Test add, commit, pull, clone. 151 """ 152 self.main_repo._edit() 153 self.main_repo.add() 154 self.main_repo.commit() 155 self.dependent_repo.get_main_tot() 156 self.assertEquals(self.dependent_repo.commit_hash, 157 self.main_repo.commit_hash, 158 msg=(("hashes don't match after clone, main and dependent repo" 159 "out of sync: %r != %r") % 160 (self.dependent_repo.commit_hash, 161 self.main_repo.commit_hash))) 162 163 self.main_repo._edit(msg='foobar') 164 self.main_repo.commit() 165 self.dependent_repo.get_main_tot() 166 self.assertEquals(self.dependent_repo.commit_hash, 167 self.main_repo.commit_hash, 168 msg=(("hashes don't match after pull, main and dependent repo" 169 "out of sync: %r != %r") % 170 (self.dependent_repo.commit_hash, 171 self.main_repo.commit_hash))) 172 173 174 def testGitUrlClone(self): 175 """ 176 Test that git clone raises a ValueError if giturl is unset. 177 """ 178 self.dependent_repo.git_repo_manager._giturl = None 179 self.assertRaises(ValueError, 180 self.dependent_repo.git_repo_manager.clone) 181 182 183 def testGitUrlPull(self): 184 """ 185 Test that git pull raises a ValueError if giturl is unset. 186 """ 187 self.dependent_repo.git_repo_manager._giturl = None 188 self.assertRaises(ValueError, 189 self.dependent_repo.git_repo_manager.pull) 190 191 192 def testGitUrlFetch(self): 193 """ 194 Test that git fetch raises a ValueError if giturl is unset. 195 """ 196 self.dependent_repo.git_repo_manager._giturl = None 197 self.assertRaises(ValueError, 198 self.dependent_repo.git_repo_manager.fetch_remote) 199 200 201if __name__ == '__main__': 202 unittest.main() 203