| OLD | NEW |
| 1 # | |
| 2 # Copyright 2007 IBM Corp. Released under the GPL v2 | |
| 3 # Authors: Ryan Harper <ryanh@us.ibm.com> | |
| 4 # | |
| 5 | |
| 6 """ | 1 """ |
| 7 This module defines a class for handling building from git repos | 2 This module defines a class for handling building from git repos |
| 3 |
| 4 @author: Ryan Harper (ryanh@us.ibm.com) |
| 5 @copyright: IBM 2007 |
| 8 """ | 6 """ |
| 9 | 7 |
| 10 __author__ = """ | 8 import os, warnings, logging |
| 11 ryanh@us.ibm.com (Ryan Harper) | 9 from autotest_lib.client.common_lib import error, revision_control |
| 12 """ | 10 from autotest_lib.client.bin import os_dep |
| 13 | |
| 14 | |
| 15 import os | |
| 16 from autotest_lib.client.common_lib import error | |
| 17 from autotest_lib.server import utils, installable_object | 11 from autotest_lib.server import utils, installable_object |
| 18 | 12 |
| 19 | 13 |
| 20 class GitRepo(installable_object.InstallableObject): | 14 class InstallableGitRepo(installable_object.InstallableObject): |
| 21 """ | 15 """ |
| 22 This class represents a git repo. | 16 This class helps to pick a git repo and install it in a host. |
| 23 | |
| 24 It is used to pull down a local copy of a git repo, check if the local | |
| 25 repo is up-to-date, if not update. It delegates the install to | |
| 26 implementation classes. | |
| 27 | |
| 28 """ | 17 """ |
| 29 | 18 def __init__(self, repodir, giturl, weburl=None): |
| 30 def __init__(self, repodir, giturl, weburl): | 19 self.repodir = repodir |
| 31 super(installable_object.InstallableObject, self).__init__() | |
| 32 if repodir is None: | |
| 33 e_msg = 'You must provide a directory to hold the git repository' | |
| 34 raise ValueError(e_msg) | |
| 35 self.repodir = utils.sh_escape(repodir) | |
| 36 if giturl is None: | |
| 37 raise ValueError('You must provide a git URL to the repository') | |
| 38 self.giturl = giturl | 20 self.giturl = giturl |
| 39 if weburl is None: | |
| 40 raise ValueError('You must provide a http URL to the repository') | |
| 41 self.weburl = weburl | 21 self.weburl = weburl |
| 42 | 22 self.git_repo = revision_control.GitRepo(self.repodir, self.giturl, |
| 43 # path to .git dir | 23 self.weburl) |
| 44 self.gitpath = utils.sh_escape(os.path.join(self.repodir,'.git')) | |
| 45 | |
| 46 # base git command , pointing to gitpath git dir | |
| 47 self.gitcmdbase = 'git --git-dir=%s' % self.gitpath | |
| 48 | |
| 49 # default to same remote path as local | 24 # default to same remote path as local |
| 50 self.__build = os.path.dirname(self.repodir) | 25 self._build = os.path.dirname(self.repodir) |
| 51 | |
| 52 | |
| 53 def run(self, command, timeout=None, ignore_status=False): | |
| 54 return utils.run(r'%s' % (utils.sh_escape(command)), | |
| 55 timeout, ignore_status) | |
| 56 | 26 |
| 57 | 27 |
| 58 # base install method | 28 # base install method |
| 59 def install(self, host, builddir=None): | 29 def install(self, host, builddir=None): |
| 30 """ |
| 31 Install a git repo in a host. It works by pushing the downloaded source |
| 32 code to the host. |
| 33 |
| 34 @param host: Host object. |
| 35 @param builddir: Directory on the host filesystem that will host the |
| 36 source code. |
| 37 """ |
| 60 # allow override of target remote dir | 38 # allow override of target remote dir |
| 61 if builddir: | 39 if builddir: |
| 62 self.__build = builddir | 40 self._build = builddir |
| 63 | 41 |
| 64 # push source to host for install | 42 # push source to host for install |
| 65 print 'pushing %s to host:%s' %(self.source_material, self.__build) | 43 logging.info('Pushing code dir %s to host %s', self.source_material, |
| 66 host.send_file(self.source_material, self.__build) | 44 self._build) |
| 45 host.send_file(self.source_material, self._build) |
| 67 | 46 |
| 68 | 47 |
| 69 def gitcmd(self, cmd, ignore_status=False): | 48 def gitcmd(self, cmd, ignore_status=False): |
| 70 return self.run('%s %s'%(self.gitcmdbase, cmd), | 49 """ |
| 71 ignore_status=ignore_status) | 50 Wrapper for a git command. |
| 51 |
| 52 @param cmd: Git subcommand (ex 'clone'). |
| 53 @param ignore_status: Whether we should supress error.CmdError |
| 54 exceptions if the command did return exit code !=0 (True), or |
| 55 not supress them (False). |
| 56 """ |
| 57 return self.git_repo.gitcmd(cmd, ignore_status) |
| 72 | 58 |
| 73 | 59 |
| 74 def get(self, **kwargs): | 60 def get(self, **kwargs): |
| 75 """ | 61 """ |
| 76 This method overrides baseclass get so we can do proper git | 62 This method overrides baseclass get so we can do proper git |
| 77 clone/pulls, and check for updated versions. The result of | 63 clone/pulls, and check for updated versions. The result of |
| 78 this method will leave an up-to-date version of git repo at | 64 this method will leave an up-to-date version of git repo at |
| 79 'giturl' in 'repodir' directory to be used by build/install | 65 'giturl' in 'repodir' directory to be used by build/install |
| 80 methods. | 66 methods. |
| 67 |
| 68 @param **kwargs: Dictionary of parameters to the method get. |
| 81 """ | 69 """ |
| 82 | |
| 83 if not self.is_repo_initialized(): | |
| 84 # this is your first time ... | |
| 85 print 'cloning repo...' | |
| 86 cmd = 'clone %s %s ' %(self.giturl, self.repodir) | |
| 87 rv = self.gitcmd(cmd, True) | |
| 88 if rv.exit_status != 0: | |
| 89 print rv.stderr | |
| 90 raise error.CmdError('Failed to clone git url', rv) | |
| 91 else: | |
| 92 print rv.stdout | |
| 93 | |
| 94 else: | |
| 95 # exiting repo, check if we're up-to-date | |
| 96 if self.is_out_of_date(): | |
| 97 print 'updating repo...' | |
| 98 rv = self.gitcmd('pull', True) | |
| 99 if rv.exit_status != 0: | |
| 100 print rv.stderr | |
| 101 e_msg = 'Failed to pull git repo data' | |
| 102 raise error.CmdError(e_msg, rv) | |
| 103 else: | |
| 104 print 'repo up-to-date' | |
| 105 | |
| 106 | |
| 107 # remember where the source is | |
| 108 self.source_material = self.repodir | 70 self.source_material = self.repodir |
| 71 return self.git_repo.get(**kwargs) |
| 109 | 72 |
| 110 | 73 |
| 111 def get_local_head(self): | 74 def get_local_head(self): |
| 112 cmd = 'log --max-count=1' | 75 """ |
| 113 gitlog = self.gitcmd(cmd).stdout | 76 Get the top commit hash of the current local git branch. |
| 114 | 77 |
| 115 # parsing the commit checksum out of git log 's first entry. | 78 @return: Top commit hash of local git branch |
| 116 # Output looks like: | 79 """ |
| 117 # | 80 return self.git_repo.get_local_head() |
| 118 # commit 1dccba29b4e5bf99fb98c324f952386dda5b097f | |
| 119 # Merge: 031b69b... df6af41... | |
| 120 # Author: Avi Kivity <avi@qumranet.com> | |
| 121 # Date: Tue Oct 23 10:36:11 2007 +0200 | |
| 122 # | |
| 123 # Merge home:/home/avi/kvm/linux-2.6 | |
| 124 return str(gitlog.split('\n')[0]).split()[1] | |
| 125 | 81 |
| 126 | 82 |
| 127 def get_remote_head(self): | 83 def get_remote_head(self): |
| 128 def __needs_refresh(lines): | 84 """ |
| 129 tag = '<meta http-equiv="refresh" content="0"/>' | 85 Get the top commit hash of the current remote git branch. |
| 130 if len(filter(lambda x: x.startswith(tag), lines)) > 0: | |
| 131 return True | |
| 132 | 86 |
| 133 return False | 87 @return: Top commit hash of remote git branch |
| 134 | 88 """ |
| 135 | 89 return self.git_repo.get_remote_head() |
| 136 # scan git web interface for revision HEAD's commit tag | |
| 137 gitwebaction=';a=commit;h=HEAD' | |
| 138 url = self.weburl+gitwebaction | |
| 139 max_refresh = 4 | |
| 140 r = 0 | |
| 141 | |
| 142 print 'checking %s for changes' %(url) | |
| 143 u = utils.urlopen(url) | |
| 144 lines = u.read().split('\n') | |
| 145 | |
| 146 while __needs_refresh(lines) and r < max_refresh: | |
| 147 print 'refreshing url' | |
| 148 r = r+1 | |
| 149 u = utils.urlopen(url) | |
| 150 lines = u.read().split('\n') | |
| 151 | |
| 152 if r >= max_refresh: | |
| 153 e_msg = 'Failed to get remote repo status, refreshed %s times' % r | |
| 154 raise IndexError(e_msg) | |
| 155 | |
| 156 # looking for a line like: | |
| 157 # <tr><td>commit</td><td # class="sha1">aadea67210c8b9e7a57744a1c2845501
d2cdbac7</td></tr> | |
| 158 commit_filter = lambda x: x.startswith('<tr><td>commit</td>') | |
| 159 commit_line = filter(commit_filter, lines) | |
| 160 | |
| 161 # extract the sha1 sum from the commit line | |
| 162 return str(commit_line).split('>')[4].split('<')[0] | |
| 163 | 90 |
| 164 | 91 |
| 165 def is_out_of_date(self): | 92 def is_out_of_date(self): |
| 166 local_head = self.get_local_head() | 93 """ |
| 167 remote_head = self.get_remote_head() | 94 Return whether this branch is out of date with regards to remote branch. |
| 168 | 95 |
| 169 # local is out-of-date, pull | 96 @return: False, if the branch is outdated, True if it is current. |
| 170 if local_head != remote_head: | 97 """ |
| 171 return True | 98 return self.git_repo.is_out_of_date() |
| 172 | |
| 173 return False | |
| 174 | 99 |
| 175 | 100 |
| 176 def is_repo_initialized(self): | 101 def is_repo_initialized(self): |
| 177 # if we fail to get a rv of 0 out of the git log command | 102 """ |
| 178 # then the repo is bogus | 103 Return whether the git repo was already initialized (has a top commit). |
| 179 | 104 |
| 180 cmd = 'log --max-count=1' | 105 @return: False, if the repo was initialized, True if it was not. |
| 181 rv = self.gitcmd(cmd, True) | 106 """ |
| 182 if rv.exit_status == 0: | 107 return self.git_repo.is_repo_initialized() |
| 183 return True | |
| 184 | 108 |
| 185 return False | 109 |
| 110 def get_revision(self): |
| 111 """ |
| 112 Return current HEAD commit id |
| 113 """ |
| 114 return self.git_repo.get_revision() |
| 115 |
| 116 |
| 117 def checkout(self, remote, local=None): |
| 118 """ |
| 119 Check out the git commit id, branch, or tag given by remote. |
| 120 |
| 121 Optional give the local branch name as local. |
| 122 |
| 123 @param remote: Remote commit hash |
| 124 @param local: Local commit hash |
| 125 @note: For git checkout tag git version >= 1.5.0 is required |
| 126 """ |
| 127 return self.git_repo.checkout(remote, local) |
| 128 |
| 129 |
| 130 def get_branch(self, all=False, remote_tracking=False): |
| 131 """ |
| 132 Show the branches. |
| 133 |
| 134 @param all: List both remote-tracking branches and local branches (True) |
| 135 or only the local ones (False). |
| 136 @param remote_tracking: Lists the remote-tracking branches. |
| 137 """ |
| 138 return self.git_repo.get_branch(all, remote_tracking) |
| OLD | NEW |