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