diff --git a/doc/source/user/jobs.rst b/doc/source/user/jobs.rst index c2c376ee64..db13460a85 100644 --- a/doc/source/user/jobs.rst +++ b/doc/source/user/jobs.rst @@ -107,8 +107,163 @@ Zuul Variables ~~~~~~~~~~~~~~ Zuul supplies not only the variables specified by the job definition -to Ansible, but also some variables from the executor itself. They -are: +to Ansible, but also some variables from the Zuul itself. + +When a pipeline is triggered an action, it enqueues items which may +vary based on the pipeline's configuration. For example, when a new +change is created, that change may be enqueued into the pipeline, +while a tag may be enqueued into the pipeline when it is pushed. + +Information about these items is available to jobs. All of the items +enqueued in a pipeline are git references, and therefore share some +attributes in common. But other attributes may vary based on the type +of item. + +All items provide the following information as Ansible variables: + +**zuul.uuid** + The UUID of the build. A build is a single execution of a job. + When an item is enqueued into a pipeline, this usually results in + one build of each job configured for that item's project. However, + items may be re-enqueued in which case another build may run. In + dependent pipelines, the same job may run multiple times for the + same item as circumstances change ahead in the queue. Each time a + job is run, for whatever reason, it is acompanied with a new + unique id. + +.. TODO: rename build + +**zuul.buildset** + The build set UUID. When Zuul runs jobs for an item, the collection + of those jobs is known as a buildset. If the configuration of items + ahead in a dependent pipeline changes, Zuul creates a new buildset + and restarts all of the jobs. + +**zuul.ref** + The git ref of the item. This will be the full path (e.g., + 'refs/heads/master' or 'refs/changes/...'). + +**zuul.pipeline** + The name of the pipeline in which the job is being run. + +**zuul.job** + The name of the job being run. + +**zuul.project** + The item's project. This is a data structure with the following + fields: + +**zuul.project.name** + The name of the project, excluding hostname. E.g., `org/project`. + +**zuul.project.canonical_hostname** + The canonical hostname where the project lives. E.g., + `git.example.com`. + +**zuul.project.canonical_name** + The full canonical name of the project including hostname. E.g., + `git.example.com/org/project`. + +**zuul.tenant** + The name of the current Zuul tenant. + +**zuul.tags** + A list of tags associated with the job. Not to be confused with git + tags, these are simply free-form text fields that can be used by the + job for reporting or classification purposes. + +.. TODO: rename jobtags + +**zuul.items** + A data structure representing the items being tested with this + change. + +.. TODO: implement and document items + + +Change Items +++++++++++++ + +A change to the repository. Most often, this will be a git reference +which has not yet been merged into the repository (e.g., a gerrit +change or a GitHub pull request). The following additional variables +are available: + +**zuul.branch** + The target branch of the change (without the `refs/heads/` prefix). + +**zuul.change** + The identifier for the change. + +**zuul.patchset** + The patchset identifier for the change. If a change is revised, + this will have a different value. + +Branch Items +++++++++++++ + +This represents a branch tip. This item may have been enqueued +because the branch was updated (via a change having merged, or a +direct push). Or it may have been enqueued by a timer for the purpose +of verifying the current condition of the branch. The following +additional variables are available: + +**zuul.branch** + The name of the item's branch (without the `refs/heads/` prefix). + +**zuul.oldrev** + If the item was enqueued as the result of a change merging or being + pushed to the branch, the git sha of the old revision will be + included here. Otherwise, this value will not be present. + +**zuul.newrev** + If the item was enqueued as the result of a change merging or being + pushed to the branch, the git sha of the new revision will be + included here. Otherwise, this value will not be present. + +Tag Items ++++++++++ + +This represents a git tag. The item may have been enqueued because a +tag was created or deleted. The following additional variables are +available: + +**zuul.tag** + The name of the item's tag (without the `refs/tags/` prefix). + +**zuul.oldrev** + If the item was enqueued as the result of a tag being created or + deleted the git sha of the old revision will be included here. + Otherwise, this value will not be present. + +**zuul.newrev** + If the item was enqueued as the result of a tag being created or + deleted the git sha of the new revision will be included here. + Otherwise, this value will not be present. + +Ref Items ++++++++++ + +This represents a git reference that is neither a change, branch, or +tag. Note that all items include a `ref` attribute which may be used +to identify the ref. The following additional variables are +available: + +**zuul.oldrev** + If the item was enqueued as the result of a ref being created, + deleted, or changed the git sha of the old revision will be included + here. Otherwise, this value will not be present. + +**zuul.newrev** + If the item was enqueued as the result of a ref being created, + deleted, or changed the git sha of the new revision will be included + here. Otherwise, this value will not be present. + +Working Directory ++++++++++++++++++ + +Additionally, some information about the working directory and the +executor running the job is available: **zuul.executor.hostname** The hostname of the executor. diff --git a/tests/base.py b/tests/base.py index fb946385fe..484b9e5d68 100755 --- a/tests/base.py +++ b/tests/base.py @@ -1068,8 +1068,10 @@ class BuildHistory(object): self.__dict__.update(kw) def __repr__(self): - return ("" % - (self.result, self.name, self.uuid, self.changes)) + return ("" % + (self.result, self.name, self.uuid, + self.changes, self.ref)) class FakeStatsd(threading.Thread): @@ -1344,6 +1346,7 @@ class RecordingAnsibleJob(zuul.executor.server.AnsibleJob): self.executor_server.build_history.append( BuildHistory(name=build.name, result=result, changes=build.changes, node=build.node, uuid=build.unique, + ref=build.parameters['zuul']['ref'], parameters=build.parameters, jobdir=build.jobdir, pipeline=build.parameters['ZUUL_PIPELINE']) ) diff --git a/tests/fixtures/layouts/idle.yaml b/tests/fixtures/layouts/idle.yaml index 60f8ed1fba..49c45acc10 100644 --- a/tests/fixtures/layouts/idle.yaml +++ b/tests/fixtures/layouts/idle.yaml @@ -6,20 +6,14 @@ - time: '* * * * * */1' - job: - name: project-bitrot-stable-old + name: project-bitrot nodes: - name: static label: ubuntu-xenial -- job: - name: project-bitrot-stable-older - nodes: - - name: static - label: ubuntu-trusty - - project: name: org/project periodic: jobs: - - project-bitrot-stable-old - - project-bitrot-stable-older + - project-bitrot + diff --git a/tests/fixtures/layouts/no-timer.yaml b/tests/fixtures/layouts/no-timer.yaml index 12eaa35384..05f17d2589 100644 --- a/tests/fixtures/layouts/no-timer.yaml +++ b/tests/fixtures/layouts/no-timer.yaml @@ -24,17 +24,11 @@ name: project-test1 - job: - name: project-bitrot-stable-old + name: project-bitrot nodes: - name: static label: ubuntu-xenial -- job: - name: project-bitrot-stable-older - nodes: - - name: static - label: ubuntu-trusty - - project: name: org/project check: @@ -42,5 +36,4 @@ - project-test1 periodic: jobs: - - project-bitrot-stable-old - - project-bitrot-stable-older + - project-bitrot diff --git a/tests/fixtures/layouts/repo-checkout-timer-override.yaml b/tests/fixtures/layouts/repo-checkout-timer-override.yaml new file mode 100644 index 0000000000..594d74c5a9 --- /dev/null +++ b/tests/fixtures/layouts/repo-checkout-timer-override.yaml @@ -0,0 +1,19 @@ +- pipeline: + name: periodic + manager: independent + trigger: + timer: + - time: '* * * * * */1' + +- job: + name: integration + branches: master + override-branch: stable/havana + required-projects: + - org/project1 + +- project: + name: org/project1 + periodic: + jobs: + - integration diff --git a/tests/fixtures/layouts/repo-checkout-timer.yaml b/tests/fixtures/layouts/repo-checkout-timer.yaml index d5917d13bb..3c4d030c39 100644 --- a/tests/fixtures/layouts/repo-checkout-timer.yaml +++ b/tests/fixtures/layouts/repo-checkout-timer.yaml @@ -7,7 +7,6 @@ - job: name: integration - override-branch: stable/havana required-projects: - org/project1 diff --git a/tests/fixtures/layouts/timer.yaml b/tests/fixtures/layouts/timer.yaml index 883c32e63c..dbce51637d 100644 --- a/tests/fixtures/layouts/timer.yaml +++ b/tests/fixtures/layouts/timer.yaml @@ -25,17 +25,11 @@ name: project-test2 - job: - name: project-bitrot-stable-old + name: project-bitrot nodes: - name: static label: ubuntu-xenial -- job: - name: project-bitrot-stable-older - nodes: - - name: static - label: ubuntu-trusty - - project: name: org/project check: @@ -44,5 +38,4 @@ - project-test2 periodic: jobs: - - project-bitrot-stable-old - - project-bitrot-stable-older + - project-bitrot diff --git a/tests/unit/test_executor.py b/tests/unit/test_executor.py index 7b768021a7..4700bd1191 100755 --- a/tests/unit/test_executor.py +++ b/tests/unit/test_executor.py @@ -248,6 +248,46 @@ class TestExecutorRepos(ZuulTestCase): self.assertBuildStates(states, projects) + def test_periodic_override(self): + # This test can not use simple_layout because it must start + # with a configuration which does not include a + # timer-triggered job so that we have an opportunity to set + # the hold flag before the first job. + + # This tests that we can override the branch in a timer + # trigger (mostly to ensure backwards compatability for jobs). + self.executor_server.hold_jobs_in_build = True + # Start timer trigger - also org/project + self.commitConfigUpdate('common-config', + 'layouts/repo-checkout-timer-override.yaml') + self.sched.reconfigure(self.config) + + p1 = 'review.example.com/org/project1' + projects = [p1] + self.create_branch('org/project1', 'stable/havana') + + # The pipeline triggers every second, so we should have seen + # several by now. + time.sleep(5) + self.waitUntilSettled() + + # Stop queuing timer triggered jobs so that the assertions + # below don't race against more jobs being queued. + self.commitConfigUpdate('common-config', + 'layouts/repo-checkout-no-timer.yaml') + self.sched.reconfigure(self.config) + + self.assertEquals(1, len(self.builds), "One build is running") + + upstream = self.getUpstreamRepos(projects) + states = [ + {p1: dict(commit=str(upstream[p1].commit('stable/havana')), + branch='stable/havana'), + }, + ] + + self.assertBuildStates(states, projects) + def test_periodic(self): # This test can not use simple_layout because it must start # with a configuration which does not include a @@ -274,14 +314,19 @@ class TestExecutorRepos(ZuulTestCase): 'layouts/repo-checkout-no-timer.yaml') self.sched.reconfigure(self.config) - self.assertEquals(1, len(self.builds), "One build is running") + self.assertEquals(2, len(self.builds), "Two builds are running") upstream = self.getUpstreamRepos(projects) states = [ {p1: dict(commit=str(upstream[p1].commit('stable/havana')), branch='stable/havana'), }, + {p1: dict(commit=str(upstream[p1].commit('master')), + branch='master'), + }, ] + if self.builds[0].parameters['zuul']['ref'] == 'refs/heads/master': + states = list(reversed(states)) self.assertBuildStates(states, projects) diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py index 0cfe3dad4a..849357065e 100644 --- a/tests/unit/test_github_driver.py +++ b/tests/unit/test_github_driver.py @@ -12,11 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. +import os import re from testtools.matchers import MatchesRegex, StartsWith import urllib import time +import git + from tests.base import ZuulTestCase, simple_layout, random_sha1 @@ -94,7 +97,16 @@ class TestGithubDriver(ZuulTestCase): def test_tag_event(self): self.executor_server.hold_jobs_in_build = True - sha = random_sha1() + self.create_branch('org/project', 'tagbranch') + files = {'README.txt': 'test'} + self.addCommitToRepo('org/project', 'test tag', + files, branch='tagbranch', tag='newtag') + path = os.path.join(self.upstream_root, 'org/project') + repo = git.Repo(path) + tag = repo.tags['newtag'] + sha = tag.commit.hexsha + del repo + self.fake_github.emitEvent( self.fake_github.getPushEvent('org/project', 'refs/tags/newtag', new_rev=sha)) diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py index d9cf839a9a..3e60eadf6d 100755 --- a/tests/unit/test_scheduler.py +++ b/tests/unit/test_scheduler.py @@ -1806,17 +1806,17 @@ class TestScheduler(ZuulTestCase): self.commitConfigUpdate('common-config', 'layouts/no-timer.yaml') self.sched.reconfigure(self.config) - self.assertEqual(len(self.builds), 2, "Two timer jobs") + self.assertEqual(len(self.builds), 1, "One timer job") A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1)) self.waitUntilSettled() - self.assertEqual(len(self.builds), 3, "One change plus two timer jobs") + self.assertEqual(len(self.builds), 2, "One change plus one timer job") self.fake_gerrit.addEvent(A.getChangeAbandonedEvent()) self.waitUntilSettled() - self.assertEqual(len(self.builds), 2, "Two timer jobs remain") + self.assertEqual(len(self.builds), 1, "One timer job remains") self.executor_server.release() self.waitUntilSettled() @@ -2777,6 +2777,7 @@ class TestScheduler(ZuulTestCase): # with a configuration which does not include a # timer-triggered job so that we have an opportunity to set # the hold flag before the first job. + self.create_branch('org/project', 'stable') self.executor_server.hold_jobs_in_build = True self.commitConfigUpdate('common-config', 'layouts/timer.yaml') self.sched.reconfigure(self.config) @@ -2803,10 +2804,12 @@ class TestScheduler(ZuulTestCase): self.executor_server.release() self.waitUntilSettled() - self.assertEqual(self.getJobFromHistory( - 'project-bitrot-stable-old').result, 'SUCCESS') - self.assertEqual(self.getJobFromHistory( - 'project-bitrot-stable-older').result, 'SUCCESS') + self.assertHistory([ + dict(name='project-bitrot', result='SUCCESS', + ref='refs/heads/master'), + dict(name='project-bitrot', result='SUCCESS', + ref='refs/heads/stable'), + ], ordered=False) data = json.loads(data) status_jobs = set() @@ -2816,8 +2819,7 @@ class TestScheduler(ZuulTestCase): for change in head: for job in change['jobs']: status_jobs.add(job['name']) - self.assertIn('project-bitrot-stable-old', status_jobs) - self.assertIn('project-bitrot-stable-older', status_jobs) + self.assertIn('project-bitrot', status_jobs) def test_idle(self): "Test that frequent periodic jobs work" @@ -2846,12 +2848,12 @@ class TestScheduler(ZuulTestCase): 'layouts/no-timer.yaml') self.sched.reconfigure(self.config) self.waitUntilSettled() - self.assertEqual(len(self.builds), 2, + self.assertEqual(len(self.builds), 1, 'Timer builds iteration #%d' % x) self.executor_server.release('.*') self.waitUntilSettled() self.assertEqual(len(self.builds), 0) - self.assertEqual(len(self.history), x * 2) + self.assertEqual(len(self.history), x) @simple_layout('layouts/smtp.yaml') def test_check_smtp_pool(self): diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py index 734c45c954..9c7ffea96a 100644 --- a/tests/unit/test_v3.py +++ b/tests/unit/test_v3.py @@ -667,7 +667,8 @@ class TestPrePlaybooks(AnsibleZuulTestCase): self.assertFalse(os.path.exists(pre_flag_path)) post_flag_path = os.path.join(self.test_root, build.uuid + '.post.flag') - self.assertTrue(os.path.exists(post_flag_path)) + self.assertTrue(os.path.exists(post_flag_path), + "The file %s should exist" % post_flag_path) class TestBrokenConfig(ZuulTestCase): diff --git a/zuul/driver/gerrit/gerritconnection.py b/zuul/driver/gerrit/gerritconnection.py index 924a42f400..5ad4e7a0ff 100644 --- a/zuul/driver/gerrit/gerritconnection.py +++ b/zuul/driver/gerrit/gerritconnection.py @@ -26,7 +26,7 @@ import queue import voluptuous as v from zuul.connection import BaseConnection -from zuul.model import Ref +from zuul.model import Ref, Tag, Branch from zuul import exceptions from zuul.driver.gerrit.gerritmodel import GerritChange, GerritTriggerEvent @@ -293,7 +293,34 @@ class GerritConnection(BaseConnection): if event.change_number: change = self._getChange(event.change_number, event.patch_number, refresh=refresh) + elif event.ref and event.ref.startswith('refs/tags/'): + project = self.source.getProject(event.project_name) + change = Tag(project) + change.tag = event.ref[len('refs/tags/'):] + change.ref = event.ref + change.oldrev = event.oldrev + change.newrev = event.newrev + change.url = self._getGitwebUrl(project, sha=event.newrev) + elif event.ref and not event.ref.startswith('refs/'): + # Gerrit ref-updated events don't have branch prefixes. + project = self.source.getProject(event.project_name) + change = Branch(project) + change.branch = event.ref + change.ref = 'refs/heads/' + event.ref + change.oldrev = event.oldrev + change.newrev = event.newrev + change.url = self._getGitwebUrl(project, sha=event.newrev) + elif event.ref and event.ref.startswith('refs/heads/'): + # From the timer trigger + project = self.source.getProject(event.project_name) + change = Branch(project) + change.ref = event.ref + change.branch = event.branch + change.oldrev = event.oldrev + change.newrev = event.newrev + change.url = self._getGitwebUrl(project, sha=event.newrev) elif event.ref: + # catch-all ref (ie, not a branch or head) project = self.source.getProject(event.project_name) change = Ref(project) change.ref = event.ref @@ -301,14 +328,8 @@ class GerritConnection(BaseConnection): change.newrev = event.newrev change.url = self._getGitwebUrl(project, sha=event.newrev) else: - project = self.source.getProject(event.project_name) - change = Ref(project) - branch = event.branch or 'master' - change.ref = 'refs/heads/%s' % branch - refs = self.getInfoRefs(project) - change.oldrev = refs[change.ref] - change.newrev = refs[change.ref] - change.url = self._getGitwebUrl(project, sha=change.newrev) + self.log.warning("Unable to get change for %s" % (event,)) + change = None return change def _getChange(self, number, patchset, refresh=False, history=None): diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py index a4a4c12156..ff113ce2bf 100644 --- a/zuul/driver/github/githubconnection.py +++ b/zuul/driver/github/githubconnection.py @@ -32,7 +32,7 @@ import github3 from github3.exceptions import MethodNotAllowed from zuul.connection import BaseConnection -from zuul.model import Ref +from zuul.model import Ref, Branch, Tag from zuul.exceptions import MergeFailure from zuul.driver.github.githubmodel import PullRequest, GithubTriggerEvent @@ -506,16 +506,21 @@ class GithubConnection(BaseConnection): change.source_event = event change.is_current_patchset = (change.pr.get('head').get('sha') == event.patch_number) - elif event.ref: - change = Ref(project) + else: + if event.ref and event.ref.startswith('refs/tags/'): + change = Tag(project) + change.tag = event.ref[len('refs/tags/'):] + elif event.ref and event.ref.startswith('refs/heads/'): + change = Branch(project) + change.branch = event.ref[len('refs/heads/'):] + else: + change = Ref(project) change.ref = event.ref change.oldrev = event.oldrev change.newrev = event.newrev change.url = self.getGitwebUrl(project, sha=event.newrev) change.source_event = event change.files = self.getPushedFileNames(event) - else: - change = Ref(project) return change def _getChange(self, project, number, patchset=None, refresh=False, diff --git a/zuul/driver/timer/__init__.py b/zuul/driver/timer/__init__.py index cdaea742e1..4489808f04 100644 --- a/zuul/driver/timer/__init__.py +++ b/zuul/driver/timer/__init__.py @@ -80,15 +80,18 @@ class TimerDriver(Driver, TriggerInterface): def _onTrigger(self, tenant, pipeline_name, timespec): for project_name in tenant.layout.project_configs.keys(): - project_hostname, project_name = project_name.split('/', 1) - event = TimerTriggerEvent() - event.type = 'timer' - event.timespec = timespec - event.forced_pipeline = pipeline_name - event.project_hostname = project_hostname - event.project_name = project_name - self.log.debug("Adding event %s" % event) - self.sched.addEvent(event) + (trusted, project) = tenant.getProject(project_name) + for branch in project.source.getProjectBranches(project): + event = TimerTriggerEvent() + event.type = 'timer' + event.timespec = timespec + event.forced_pipeline = pipeline_name + event.project_hostname = project.canonical_hostname + event.project_name = project.name + event.ref = 'refs/heads/%s' % branch + event.branch = branch + self.log.debug("Adding event %s" % event) + self.sched.addEvent(event) def stop(self): if self.apsched: diff --git a/zuul/executor/client.py b/zuul/executor/client.py index f764778999..dccb8d2610 100644 --- a/zuul/executor/client.py +++ b/zuul/executor/client.py @@ -156,19 +156,26 @@ class ExecutorClient(object): canonical_name=item.change.project.canonical_name) zuul_params = dict(uuid=uuid, - ref=item.current_build_set.ref, + buildset=item.current_build_set.uuid, + ref=item.change.ref, pipeline=pipeline.name, job=job.name, project=project, tenant=tenant.name, tags=' '.join(sorted(job.tags))) - if hasattr(item.change, 'branch'): zuul_params['branch'] = item.change.branch + if hasattr(item.change, 'tag'): + zuul_params['tag'] = item.change.tag if hasattr(item.change, 'number'): zuul_params['change'] = item.change.number if hasattr(item.change, 'patchset'): zuul_params['patchset'] = item.change.patchset + if hasattr(item.change, 'oldrev'): + zuul_params['oldrev'] = item.change.oldrev + if hasattr(item.change, 'newrev'): + zuul_params['newrev'] = item.change.newrev + # Legacy environment variables params = dict(ZUUL_UUID=uuid, ZUUL_PROJECT=item.change.project.name) diff --git a/zuul/executor/server.py b/zuul/executor/server.py index f291dce9cd..6c7ceb44b6 100644 --- a/zuul/executor/server.py +++ b/zuul/executor/server.py @@ -838,8 +838,18 @@ class AnsibleJob(object): for project in args['projects']: repo = repos[project['canonical_name']] + # If this project is the Zuul project and this is a ref + # rather than a change, checkout the ref. + if (project['canonical_name'] == + args['zuul']['project']['canonical_name'] and + (not args['zuul'].get('branch')) and + args['zuul'].get('ref')): + ref = args['zuul']['ref'] + else: + ref = None self.checkoutBranch(repo, project['name'], + ref, args['branch'], args['override_branch'], project['override_branch'], @@ -909,7 +919,7 @@ class AnsibleJob(object): repo.setRef('refs/heads/' + branch, commit) return True - def checkoutBranch(self, repo, project_name, zuul_branch, + def checkoutBranch(self, repo, project_name, ref, zuul_branch, job_branch, project_override_branch, project_default_branch): branches = repo.getBranches() @@ -921,6 +931,16 @@ class AnsibleJob(object): self.log.info("Checking out %s job branch %s", project_name, job_branch) repo.checkoutLocalBranch(job_branch) + elif ref and ref.startswith('refs/heads/'): + b = ref[len('refs/heads/'):] + self.log.info("Checking out %s branch ref %s", + project_name, b) + repo.checkoutLocalBranch(b) + elif ref and ref.startswith('refs/tags/'): + t = ref[len('refs/tags/'):] + self.log.info("Checking out %s tag ref %s", + project_name, t) + repo.checkout(t) elif zuul_branch and zuul_branch in branches: self.log.info("Checking out %s zuul branch %s", project_name, zuul_branch) diff --git a/zuul/model.py b/zuul/model.py index ef67828421..b266c0232d 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -1817,19 +1817,17 @@ class QueueItem(object): oldrev = None newrev = None refspec = None + branch = None if hasattr(self.change, 'number'): number = self.change.number patchset = self.change.patchset refspec = self.change.refspec - branch = self.change.branch - elif hasattr(self.change, 'newrev'): + if hasattr(self.change, 'newrev'): oldrev = self.change.oldrev newrev = self.change.newrev - branch = self.change.ref - else: - oldrev = None - newrev = None - branch = None + if hasattr(self.change, 'branch'): + branch = self.change.branch + source = self.change.project.source connection_name = source.connection.connection_name project = self.change.project @@ -1855,32 +1853,26 @@ class Ref(object): self.ref = None self.oldrev = None self.newrev = None - self.files = [] - def getBasePath(self): - base_path = '' - if hasattr(self, 'ref'): - base_path = "%s/%s" % (self.newrev[:2], self.newrev) - - return base_path - def _id(self): return self.newrev def __repr__(self): rep = None if self.newrev == '0000000000000000000000000000000000000000': - rep = '' % (id(self), self._id()) - def getBasePath(self): - if hasattr(self, 'refspec'): - return "%s/%s/%s" % ( - str(self.number)[-2:], self.number, self.patchset) - return super(Change, self).getBasePath() - def equals(self, other): if self.number == other.number and self.patchset == other.patchset: return True