1 from datetime import datetime
7 from patchwork.models import Patch, Project, State
8 from patchmetrics.models import GerritChange
11 ROOT_URL = 'https://android-review.googlesource.com/gerrit/rpc'
12 # A mapping from AOSP states to our internal ones.
13 STATE_MAP = {'MERGED': 'Accepted', 'NEW': 'New', 'ABANDONED': 'Rejected'}
16 class GerritError(Exception):
17 """The gerrit server returned an error in response to a query."""
20 def jsonrpc_request(path, method, params):
22 'Accept': 'application/json,application/jsonrequest',
23 'Content-type': 'application/json; charset=UTF-8'}
30 response = json.loads(
31 urllib2.urlopen(urllib2.Request(url, data, headers)).read())
32 if 'error' in response:
33 msg = "Got an error on %s, with method=%s and params=%s: %s" % (
34 url, method, params, response['error']['message'])
35 raise GerritError(msg)
36 return response.get('result')
39 def get_aosp_change_details(change_id):
40 """Get the details of the Gerrit change with the given ID."""
41 return jsonrpc_request(
42 '/ChangeDetailService',
43 'changeDetail', [dict(id=change_id)])['change']
46 def get_aosp_changes_from(email, start_after=None, page_size=100):
47 """Get the list of changes authored by the given email address.
49 :param start_after: Only include changes whose sortKey is lower than
50 this. Use None if you want to start from the beginning.
51 :param page_size: Number of items to include in the list.
53 if start_after is None:
55 query = "owner: %s" % email
56 return jsonrpc_request(
58 'allQueryNext', [query, start_after, page_size])
61 def create_or_update_patches_from_aosp_changes(changes, author):
62 """Create or update Patch objects from the given list of changes.
64 For each of the changes in the list, make sure there's a matching Patch
65 (and GerritChange) object in our DB matching it and update it to be in
66 sync with the remote change.
68 Return a list with they IDs of the created/updated changes.
70 print "Checking changes submitted by %s ..." % author.email
72 for change in changes:
73 gerrit_id = change['id']['id']
74 last_updated_on = datetime.strptime(
75 change['lastUpdatedOn'], '%Y-%m-%d %H:%M:%S.%f000')
77 existing = GerritChange.objects.get(gerrit_id=gerrit_id)
78 except GerritChange.DoesNotExist:
80 # Must get the change's details because we need their creation date.
81 details = get_aosp_change_details(gerrit_id)
82 date_created = datetime.strptime(
83 details['createdOn'], '%Y-%m-%d %H:%M:%S.%f000')
85 id=gerrit_id, project=change['project']['key']['name'],
86 last_updated_on=last_updated_on, status=change['status'],
87 subject=change['subject'], date_created=date_created,
88 change_id=change['key']['id'])
90 create_patch_from_aosp_change(change_dict, author)
91 updated_changes.append(gerrit_id)
92 elif existing.last_updated_on < last_updated_on:
93 update_local_gerrit_change_from_aosp_change(existing, change_dict)
94 updated_changes.append(gerrit_id)
96 # The changes are sorted most recently updated first, so as soon
97 # as we see one that doesn't need to be updated in our DB we know
98 # for sure that the following ones don't need to be updated
103 return updated_changes
106 def get_state_for_aosp_status(aosp_status):
107 return State.objects.get(name=STATE_MAP[aosp_status])
110 def get_project_for_aosp_name(aosp_name):
111 name = get_project_name_for_aosp_name(aosp_name)
113 return Project.objects.get(linkname=name)
114 except Project.DoesNotExist:
115 project = Project(linkname=name, name="AOSP %s" % aosp_name,
116 listid=name, listemail=name)
121 def get_project_name_for_aosp_name(aosp_name):
122 return 'aosp-' + aosp_name.replace('/', '-')
125 def update_local_gerrit_change_from_aosp_change(gerrit_change, change_dict):
126 """Update the local GerritChange from the given change_dict."""
127 print "Updating GerritChange/Patch for %s" % gerrit_change.url
128 gerrit_change.last_updated_on = change_dict['last_updated_on']
129 assert gerrit_change.gerrit_id == change_dict['id'], (
130 "The ID of the local GerritChange (%d) doesn't match that of the "
131 "remote change (%d)" % (gerrit_change.gerrit_id, change_dict['id']))
132 # This will take care of updating the state of the Patch object linked to
134 gerrit_change.status = change_dict['status']
136 patch = gerrit_change.patch
137 patch.name = change_dict['subject']
138 # XXX: Is it possible to change the project of a gerrit change, after it's
140 # patch.project = get_project_for_aosp_name(change_dict['project'])
141 # XXX: Is this appropriate? Need to find out what can cause
142 # lastUpdatedOn to change on gerrit.
143 # patch.date_last_state_change = change_dict['last_updated_on']
147 def create_patch_from_aosp_change(change, author):
148 """Create a Patch and GerritChange for the given change and author.
150 :param change: A dict with the following keys: id, change_id, project,
151 date_created, subject, status, last_updated_on.
152 :param author: The Person who authored this change.
154 # This is the unique identifier that gerrit includes in the changes'
155 # commit message to later identify when it's committed.
156 change_id = change['change_id']
157 project = get_project_for_aosp_name(change['project'])
158 date = change['date_created']
159 name = change['subject']
160 # Patch.msgid can't be None, so we make a dummy one here.
161 msgid = email.utils.make_msgid()
162 # We don't set the patch state because it will be set when we save the
163 # GerritChange that is linked to it.
164 patch = Patch(author=author, submitter=author, date=date, name=name,
165 project=project, msgid=msgid)
167 gerrit_change = GerritChange(
168 gerrit_id=change['id'], patch=patch, status=change['status'],
169 change_id=change_id, last_updated_on=change['last_updated_on'])
172 if patch.date_last_state_change is not None:
173 # We're creating a new patch and it's already had a state change so
174 # it's best to use the last_updated_on date as date_last_state_change
175 # than to leave it as datetime.now(), which was set above when we
176 # saved the GerritChange instance.
177 patch.date_last_state_change = change['last_updated_on']
180 print "Created GerritChange/Patch for %s" % gerrit_change.url