3 from copy
import copy
, deepcopy
13 config
= configparser
.RawConfigParser()
14 config
.read(os
.path
.dirname(os
.path
.realpath(__file__
)) + "/../conf/config")
16 aur_db_host
= config
.get('database', 'host')
17 aur_db_name
= config
.get('database', 'name')
18 aur_db_user
= config
.get('database', 'user')
19 aur_db_pass
= config
.get('database', 'password')
20 aur_db_socket
= config
.get('database', 'socket')
22 repo_path
= config
.get('serve', 'repo-path')
23 repo_regex
= config
.get('serve', 'repo-regex')
25 def extract_arch_fields(pkginfo
, field
):
29 for val
in pkginfo
[field
]:
30 values
.append({"value": val
, "arch": None})
32 for arch
in ['i686', 'x86_64']:
33 if field
+ '_' + arch
in pkginfo
:
34 for val
in pkginfo
[field
+ '_' + arch
]:
35 values
.append({"value": val
, "arch": arch
})
39 def save_srcinfo(srcinfo
, db
, cur
, user
):
40 # Obtain package base ID and previous maintainer.
41 pkgbase
= srcinfo
._pkgbase
['pkgname']
42 cur
.execute("SELECT ID, MaintainerUID FROM PackageBases "
43 "WHERE Name = %s", [pkgbase
])
44 (pkgbase_id
, maintainer_uid
) = cur
.fetchone()
45 was_orphan
= not maintainer_uid
47 # Obtain the user ID of the new maintainer.
48 cur
.execute("SELECT ID FROM Users WHERE Username = %s", [user
])
49 user_id
= int(cur
.fetchone()[0])
51 # Update package base details and delete current packages.
52 cur
.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " +
53 "PackagerUID = %s, OutOfDateTS = NULL WHERE ID = %s",
54 [user_id
, pkgbase_id
])
55 cur
.execute("UPDATE PackageBases SET MaintainerUID = %s " +
56 "WHERE ID = %s AND MaintainerUID IS NULL",
57 [user_id
, pkgbase_id
])
58 cur
.execute("DELETE FROM Packages WHERE PackageBaseID = %s",
61 for pkgname
in srcinfo
.GetPackageNames():
62 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
64 if 'epoch' in pkginfo
and int(pkginfo
['epoch']) > 0:
65 ver
= '%d:%s-%s' % (int(pkginfo
['epoch']), pkginfo
['pkgver'],
68 ver
= '%s-%s' % (pkginfo
['pkgver'], pkginfo
['pkgrel'])
70 # Create a new package.
71 cur
.execute("INSERT INTO Packages (PackageBaseID, Name, " +
72 "Version, Description, URL) " +
73 "VALUES (%s, %s, %s, %s, %s)",
74 [pkgbase_id
, pkginfo
['pkgname'], ver
,
75 pkginfo
['pkgdesc'], pkginfo
['url']])
79 # Add package sources.
80 for source_info
in extract_arch_fields(pkginfo
, 'source'):
81 cur
.execute("INSERT INTO PackageSources (PackageID, Source, " +
82 "SourceArch) VALUES (%s, %s, %s)",
83 [pkgid
, source_info
['value'], source_info
['arch']])
85 # Add package dependencies.
86 for deptype
in ('depends', 'makedepends',
87 'checkdepends', 'optdepends'):
88 cur
.execute("SELECT ID FROM DependencyTypes WHERE Name = %s",
90 deptypeid
= cur
.fetchone()[0]
91 for dep_info
in extract_arch_fields(pkginfo
, deptype
):
92 depname
= re
.sub(r
'(<|=|>).*', '', dep_info
['value'])
93 depcond
= dep_info
['value'][len(depname
):]
94 deparch
= dep_info
['arch']
95 cur
.execute("INSERT INTO PackageDepends (PackageID, " +
96 "DepTypeID, DepName, DepCondition, DepArch) " +
97 "VALUES (%s, %s, %s, %s, %s)",
98 [pkgid
, deptypeid
, depname
, depcond
, deparch
])
100 # Add package relations (conflicts, provides, replaces).
101 for reltype
in ('conflicts', 'provides', 'replaces'):
102 cur
.execute("SELECT ID FROM RelationTypes WHERE Name = %s",
104 reltypeid
= cur
.fetchone()[0]
105 for rel_info
in extract_arch_fields(pkginfo
, reltype
):
106 relname
= re
.sub(r
'(<|=|>).*', '', rel_info
['value'])
107 relcond
= rel_info
['value'][len(relname
):]
108 relarch
= rel_info
['arch']
109 cur
.execute("INSERT INTO PackageRelations (PackageID, " +
110 "RelTypeID, RelName, RelCondition, RelArch) " +
111 "VALUES (%s, %s, %s, %s, %s)",
112 [pkgid
, reltypeid
, relname
, relcond
, relarch
])
114 # Add package licenses.
115 if 'license' in pkginfo
:
116 for license
in pkginfo
['license']:
117 cur
.execute("SELECT ID FROM Licenses WHERE Name = %s",
119 if cur
.rowcount
== 1:
120 licenseid
= cur
.fetchone()[0]
122 cur
.execute("INSERT INTO Licenses (Name) VALUES (%s)",
125 licenseid
= cur
.lastrowid
126 cur
.execute("INSERT INTO PackageLicenses (PackageID, " +
127 "LicenseID) VALUES (%s, %s)",
130 # Add package groups.
131 if 'groups' in pkginfo
:
132 for group
in pkginfo
['groups']:
133 cur
.execute("SELECT ID FROM Groups WHERE Name = %s",
135 if cur
.rowcount
== 1:
136 groupid
= cur
.fetchone()[0]
138 cur
.execute("INSERT INTO Groups (Name) VALUES (%s)",
141 groupid
= cur
.lastrowid
142 cur
.execute("INSERT INTO PackageGroups (PackageID, "
143 "GroupID) VALUES (%s, %s)", [pkgid
, groupid
])
145 # Add user to notification list on adoption.
147 cur
.execute("SELECT COUNT(*) FROM CommentNotify WHERE " +
148 "PackageBaseID = %s AND UserID = %s",
149 [pkgbase_id
, user_id
])
150 if cur
.fetchone()[0] == 0:
151 cur
.execute("INSERT INTO CommentNotify (PackageBaseID, UserID) " +
152 "VALUES (%s, %s)", [pkgbase_id
, user_id
])
157 sys
.stderr
.write("error: %s\n" % (msg
))
160 def die_commit(msg
, commit
):
161 sys
.stderr
.write("error: The following error " +
162 "occurred when parsing commit\n")
163 sys
.stderr
.write("error: %s:\n" % (commit
))
164 sys
.stderr
.write("error: %s\n" % (msg
))
167 if len(sys
.argv
) != 4:
168 die("invalid arguments")
170 refname
= sys
.argv
[1]
171 sha1_old
= sys
.argv
[2]
172 sha1_new
= sys
.argv
[3]
174 user
= os
.environ
.get("AUR_USER")
175 pkgbase
= os
.environ
.get("AUR_PKGBASE")
177 if refname
!= "refs/heads/master":
178 die("pushing to a branch other than master is restricted")
180 repo
= pygit2
.Repository(repo_path
)
181 walker
= repo
.walk(sha1_new
, pygit2
.GIT_SORT_TOPOLOGICAL
)
182 if sha1_old
!= "0000000000000000000000000000000000000000":
183 walker
.hide(sha1_old
)
185 db
= mysql
.connector
.connect(host
=aur_db_host
, user
=aur_db_user
,
186 passwd
=aur_db_pass
, db
=aur_db_name
,
187 unix_socket
=aur_db_socket
, buffered
=True)
190 cur
.execute("SELECT Name FROM PackageBlacklist")
191 blacklist
= [row
[0] for row
in cur
.fetchall()]
193 for commit
in walker
:
194 if not '.SRCINFO' in commit
.tree
:
195 die_commit("missing .SRCINFO", commit
.id)
197 for treeobj
in commit
.tree
:
198 blob
= repo
[treeobj
.id]
200 if isinstance(blob
, pygit2
.Tree
):
201 die_commit("the repository must not contain subdirectories",
204 if not isinstance(blob
, pygit2
.Blob
):
205 die_commit("not a blob object: %s" % (treeobj
), commit
.id)
207 if blob
.size
> 250000:
208 die_commit("maximum blob size (250kB) exceeded", commit
.id)
210 srcinfo_raw
= repo
[commit
.tree
['.SRCINFO'].id].data
.decode()
211 srcinfo_raw
= srcinfo_raw
.split('\n')
212 ecatcher
= aurinfo
.CollectionECatcher()
213 srcinfo
= aurinfo
.ParseAurinfoFromIterable(srcinfo_raw
, ecatcher
)
214 errors
= ecatcher
.Errors()
216 sys
.stderr
.write("error: The following errors occurred "
217 "when parsing .SRCINFO in commit\n")
218 sys
.stderr
.write("error: %s:\n" % (commit
.id))
220 sys
.stderr
.write("error: line %d: %s\n" % error
)
223 srcinfo_pkgbase
= srcinfo
._pkgbase
['pkgname']
224 if not re
.match(repo_regex
, srcinfo_pkgbase
):
225 die_commit('invalid pkgbase: %s' % (srcinfo_pkgbase
), commit
.id)
227 for pkgname
in srcinfo
.GetPackageNames():
228 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
230 for field
in ('pkgver', 'pkgrel', 'pkgname', 'pkgdesc', 'url'):
231 if not field
in pkginfo
:
232 die_commit('missing mandatory field: %s' % (field
), commit
.id)
234 if 'epoch' in pkginfo
and not pkginfo
['epoch'].isdigit():
235 die_commit('invalid epoch: %s' % (pkginfo
['epoch']), commit
.id)
237 if not re
.match(r
'[a-z0-9][a-z0-9\.+_-]*$', pkginfo
['pkgname']):
238 die_commit('invalid package name: %s' % (pkginfo
['pkgname']),
241 if not re
.match(r
'(?:http|ftp)s?://.*', pkginfo
['url']):
242 die_commit('invalid URL: %s' % (pkginfo
['url']), commit
.id)
244 for field
in ('pkgname', 'pkgdesc', 'url'):
245 if len(pkginfo
[field
]) > 255:
246 die_commit('%s field too long: %s' % (field
, pkginfo
[field
]),
249 for field
in ('install', 'changelog'):
250 if field
in pkginfo
and not pkginfo
[field
] in commit
.tree
:
251 die_commit('missing %s file: %s' % (field
, pkginfo
[field
]),
254 for field
in extract_arch_fields(pkginfo
, 'source'):
255 fname
= field
['value']
256 if "://" in fname
or "lp:" in fname
:
258 if not fname
in commit
.tree
:
259 die_commit('missing source file: %s' % (fname
), commit
.id)
261 srcinfo_raw
= repo
[repo
[sha1_new
].tree
['.SRCINFO'].id].data
.decode()
262 srcinfo_raw
= srcinfo_raw
.split('\n')
263 srcinfo
= aurinfo
.ParseAurinfoFromIterable(srcinfo_raw
)
265 srcinfo_pkgbase
= srcinfo
._pkgbase
['pkgname']
266 if srcinfo_pkgbase
!= pkgbase
:
267 die('invalid pkgbase: %s' % (srcinfo_pkgbase
))
269 pkgbase
= srcinfo
._pkgbase
['pkgname']
270 cur
.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase
])
271 pkgbase_id
= cur
.fetchone()[0]
273 for pkgname
in srcinfo
.GetPackageNames():
274 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
275 pkgname
= pkginfo
['pkgname']
277 if pkgname
in blacklist
:
278 die('package is blacklisted: %s' % (pkginfo
['pkgname']))
280 cur
.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " +
281 "PackageBaseID <> %s", [pkgname
, pkgbase_id
])
282 if cur
.fetchone()[0] > 0:
283 die('cannot overwrite package: %s' % (pkgname
))
285 save_srcinfo(srcinfo
, db
, cur
, user
)
289 # Create (or update) a branch with the name of the package base for better
291 repo
.create_reference('refs/heads/' + pkgbase
, sha1_new
, True)
293 # Work around a Git bug: The HEAD ref is not updated when using gitnamespaces.
294 # This can be removed once the bug fix is included in Git mainline. See
295 # http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html
297 repo
.create_reference('refs/namespaces/' + pkgbase
+ '/HEAD', sha1_new
, True)