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 |