OLD | NEW |
| 1 # |
| 2 # Copyright 2007 IBM Corp. Released under the GPL v2 |
| 3 # Authors: Ryan Harper <ryanh@us.ibm.com> |
| 4 # |
| 5 |
1 """ | 6 """ |
2 This module defines a class for handling building from git repos | 7 This module defines a class for handling building from git repos |
3 | |
4 @author: Ryan Harper (ryanh@us.ibm.com) | |
5 @copyright: IBM 2007 | |
6 """ | 8 """ |
7 | 9 |
8 import os, warnings, logging | 10 __author__ = """ |
9 from autotest_lib.client.common_lib import error, revision_control | 11 ryanh@us.ibm.com (Ryan Harper) |
10 from autotest_lib.client.bin import os_dep | 12 """ |
| 13 |
| 14 |
| 15 import os |
| 16 from autotest_lib.client.common_lib import error |
11 from autotest_lib.server import utils, installable_object | 17 from autotest_lib.server import utils, installable_object |
12 | 18 |
13 | 19 |
14 class InstallableGitRepo(installable_object.InstallableObject): | 20 class GitRepo(installable_object.InstallableObject): |
15 """ | 21 """ |
16 This class helps to pick a git repo and install it in a host. | 22 This class represents a git repo. |
| 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 |
17 """ | 28 """ |
18 def __init__(self, repodir, giturl, weburl=None): | 29 |
19 self.repodir = repodir | 30 def __init__(self, repodir, giturl, weburl): |
| 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') |
20 self.giturl = giturl | 38 self.giturl = giturl |
| 39 if weburl is None: |
| 40 raise ValueError('You must provide a http URL to the repository') |
21 self.weburl = weburl | 41 self.weburl = weburl |
22 self.git_repo = revision_control.GitRepo(self.repodir, self.giturl, | 42 |
23 self.weburl) | 43 # path to .git dir |
| 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 |
24 # default to same remote path as local | 49 # default to same remote path as local |
25 self._build = os.path.dirname(self.repodir) | 50 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) |
26 | 56 |
27 | 57 |
28 # base install method | 58 # base install method |
29 def install(self, host, builddir=None): | 59 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 """ | |
38 # allow override of target remote dir | 60 # allow override of target remote dir |
39 if builddir: | 61 if builddir: |
40 self._build = builddir | 62 self.__build = builddir |
41 | 63 |
42 # push source to host for install | 64 # push source to host for install |
43 logging.info('Pushing code dir %s to host %s', self.source_material, | 65 print 'pushing %s to host:%s' %(self.source_material, self.__build) |
44 self._build) | 66 host.send_file(self.source_material, self.__build) |
45 host.send_file(self.source_material, self._build) | |
46 | 67 |
47 | 68 |
48 def gitcmd(self, cmd, ignore_status=False): | 69 def gitcmd(self, cmd, ignore_status=False): |
49 """ | 70 return self.run('%s %s'%(self.gitcmdbase, cmd), |
50 Wrapper for a git command. | 71 ignore_status=ignore_status) |
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) | |
58 | 72 |
59 | 73 |
60 def get(self, **kwargs): | 74 def get(self, **kwargs): |
61 """ | 75 """ |
62 This method overrides baseclass get so we can do proper git | 76 This method overrides baseclass get so we can do proper git |
63 clone/pulls, and check for updated versions. The result of | 77 clone/pulls, and check for updated versions. The result of |
64 this method will leave an up-to-date version of git repo at | 78 this method will leave an up-to-date version of git repo at |
65 'giturl' in 'repodir' directory to be used by build/install | 79 'giturl' in 'repodir' directory to be used by build/install |
66 methods. | 80 methods. |
| 81 """ |
67 | 82 |
68 @param **kwargs: Dictionary of parameters to the method get. | 83 if not self.is_repo_initialized(): |
69 """ | 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 |
70 self.source_material = self.repodir | 108 self.source_material = self.repodir |
71 return self.git_repo.get(**kwargs) | |
72 | 109 |
73 | 110 |
74 def get_local_head(self): | 111 def get_local_head(self): |
75 """ | 112 cmd = 'log --max-count=1' |
76 Get the top commit hash of the current local git branch. | 113 gitlog = self.gitcmd(cmd).stdout |
77 | 114 |
78 @return: Top commit hash of local git branch | 115 # parsing the commit checksum out of git log 's first entry. |
79 """ | 116 # Output looks like: |
80 return self.git_repo.get_local_head() | 117 # |
| 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] |
81 | 125 |
82 | 126 |
83 def get_remote_head(self): | 127 def get_remote_head(self): |
84 """ | 128 def __needs_refresh(lines): |
85 Get the top commit hash of the current remote git branch. | 129 tag = '<meta http-equiv="refresh" content="0"/>' |
| 130 if len(filter(lambda x: x.startswith(tag), lines)) > 0: |
| 131 return True |
86 | 132 |
87 @return: Top commit hash of remote git branch | 133 return False |
88 """ | 134 |
89 return self.git_repo.get_remote_head() | 135 |
| 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] |
90 | 163 |
91 | 164 |
92 def is_out_of_date(self): | 165 def is_out_of_date(self): |
93 """ | 166 local_head = self.get_local_head() |
94 Return whether this branch is out of date with regards to remote branch. | 167 remote_head = self.get_remote_head() |
95 | 168 |
96 @return: False, if the branch is outdated, True if it is current. | 169 # local is out-of-date, pull |
97 """ | 170 if local_head != remote_head: |
98 return self.git_repo.is_out_of_date() | 171 return True |
| 172 |
| 173 return False |
99 | 174 |
100 | 175 |
101 def is_repo_initialized(self): | 176 def is_repo_initialized(self): |
102 """ | 177 # if we fail to get a rv of 0 out of the git log command |
103 Return whether the git repo was already initialized (has a top commit). | 178 # then the repo is bogus |
104 | 179 |
105 @return: False, if the repo was initialized, True if it was not. | 180 cmd = 'log --max-count=1' |
106 """ | 181 rv = self.gitcmd(cmd, True) |
107 return self.git_repo.is_repo_initialized() | 182 if rv.exit_status == 0: |
| 183 return True |
108 | 184 |
109 | 185 return False |
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 |