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 parse_dep(depstring
):
40 dep
, _
, desc
= depstring
.partition(': ')
41 depname
= re
.sub(r
'(<|=|>).*', '', dep
)
42 depcond
= dep
[len(depname
):]
45 return (depname
+ ': ' + desc
, depcond
)
47 return (depname
, depcond
)
49 def save_srcinfo(srcinfo
, db
, cur
, user
):
50 # Obtain package base ID and previous maintainer.
51 pkgbase
= srcinfo
._pkgbase
['pkgname']
52 cur
.execute("SELECT ID, MaintainerUID FROM PackageBases "
53 "WHERE Name = %s", [pkgbase
])
54 (pkgbase_id
, maintainer_uid
) = cur
.fetchone()
55 was_orphan
= not maintainer_uid
57 # Obtain the user ID of the new maintainer.
58 cur
.execute("SELECT ID FROM Users WHERE Username = %s", [user
])
59 user_id
= int(cur
.fetchone()[0])
61 # Update package base details and delete current packages.
62 cur
.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " +
63 "PackagerUID = %s, OutOfDateTS = NULL WHERE ID = %s",
64 [user_id
, pkgbase_id
])
65 cur
.execute("UPDATE PackageBases SET MaintainerUID = %s " +
66 "WHERE ID = %s AND MaintainerUID IS NULL",
67 [user_id
, pkgbase_id
])
68 cur
.execute("DELETE FROM Packages WHERE PackageBaseID = %s",
71 for pkgname
in srcinfo
.GetPackageNames():
72 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
74 if 'epoch' in pkginfo
and int(pkginfo
['epoch']) > 0:
75 ver
= '{:d}:{:s}-{:s}'.format(int(pkginfo
['epoch']), pkginfo
['pkgver'],
78 ver
= '{:s}-{:s}'.format(pkginfo
['pkgver'], pkginfo
['pkgrel'])
80 for field
in ('pkgdesc', 'url'):
81 if not field
in pkginfo
:
84 # Create a new package.
85 cur
.execute("INSERT INTO Packages (PackageBaseID, Name, " +
86 "Version, Description, URL) " +
87 "VALUES (%s, %s, %s, %s, %s)",
88 [pkgbase_id
, pkginfo
['pkgname'], ver
,
89 pkginfo
['pkgdesc'], pkginfo
['url']])
93 # Add package sources.
94 for source_info
in extract_arch_fields(pkginfo
, 'source'):
95 cur
.execute("INSERT INTO PackageSources (PackageID, Source, " +
96 "SourceArch) VALUES (%s, %s, %s)",
97 [pkgid
, source_info
['value'], source_info
['arch']])
99 # Add package dependencies.
100 for deptype
in ('depends', 'makedepends',
101 'checkdepends', 'optdepends'):
102 cur
.execute("SELECT ID FROM DependencyTypes WHERE Name = %s",
104 deptypeid
= cur
.fetchone()[0]
105 for dep_info
in extract_arch_fields(pkginfo
, deptype
):
106 depname
, depcond
= parse_dep(dep_info
['value'])
107 deparch
= dep_info
['arch']
108 cur
.execute("INSERT INTO PackageDepends (PackageID, " +
109 "DepTypeID, DepName, DepCondition, DepArch) " +
110 "VALUES (%s, %s, %s, %s, %s)",
111 [pkgid
, deptypeid
, depname
, depcond
, deparch
])
113 # Add package relations (conflicts, provides, replaces).
114 for reltype
in ('conflicts', 'provides', 'replaces'):
115 cur
.execute("SELECT ID FROM RelationTypes WHERE Name = %s",
117 reltypeid
= cur
.fetchone()[0]
118 for rel_info
in extract_arch_fields(pkginfo
, reltype
):
119 relname
, relcond
= parse_dep(rel_info
['value'])
120 relarch
= rel_info
['arch']
121 cur
.execute("INSERT INTO PackageRelations (PackageID, " +
122 "RelTypeID, RelName, RelCondition, RelArch) " +
123 "VALUES (%s, %s, %s, %s, %s)",
124 [pkgid
, reltypeid
, relname
, relcond
, relarch
])
126 # Add package licenses.
127 if 'license' in pkginfo
:
128 for license
in pkginfo
['license']:
129 cur
.execute("SELECT ID FROM Licenses WHERE Name = %s",
131 if cur
.rowcount
== 1:
132 licenseid
= cur
.fetchone()[0]
134 cur
.execute("INSERT INTO Licenses (Name) VALUES (%s)",
137 licenseid
= cur
.lastrowid
138 cur
.execute("INSERT INTO PackageLicenses (PackageID, " +
139 "LicenseID) VALUES (%s, %s)",
142 # Add package groups.
143 if 'groups' in pkginfo
:
144 for group
in pkginfo
['groups']:
145 cur
.execute("SELECT ID FROM Groups WHERE Name = %s",
147 if cur
.rowcount
== 1:
148 groupid
= cur
.fetchone()[0]
150 cur
.execute("INSERT INTO Groups (Name) VALUES (%s)",
153 groupid
= cur
.lastrowid
154 cur
.execute("INSERT INTO PackageGroups (PackageID, "
155 "GroupID) VALUES (%s, %s)", [pkgid
, groupid
])
157 # Add user to notification list on adoption.
159 cur
.execute("SELECT COUNT(*) FROM CommentNotify WHERE " +
160 "PackageBaseID = %s AND UserID = %s",
161 [pkgbase_id
, user_id
])
162 if cur
.fetchone()[0] == 0:
163 cur
.execute("INSERT INTO CommentNotify (PackageBaseID, UserID) " +
164 "VALUES (%s, %s)", [pkgbase_id
, user_id
])
169 sys
.stderr
.write("error: {:s}\n".format(msg
))
172 def die_commit(msg
, commit
):
173 sys
.stderr
.write("error: The following error " +
174 "occurred when parsing commit\n")
175 sys
.stderr
.write("error: {:s}:\n".format(commit
))
176 sys
.stderr
.write("error: {:s}\n".format(msg
))
179 repo
= pygit2
.Repository(repo_path
)
181 user
= os
.environ
.get("AUR_USER")
182 pkgbase
= os
.environ
.get("AUR_PKGBASE")
183 privileged
= (os
.environ
.get("AUR_PRIVILEGED", '0') == '1')
185 if len(sys
.argv
) == 2 and sys
.argv
[1] == "restore":
186 if 'refs/heads/' + pkgbase
not in repo
.listall_references():
187 die('{:s}: repository not found: {:s}'.format(sys
.argv
[1], pkgbase
))
188 refname
= "refs/heads/master"
189 sha1_old
= sha1_new
= repo
.lookup_reference('refs/heads/' + pkgbase
).target
190 elif len(sys
.argv
) == 4:
191 refname
, sha1_old
, sha1_new
= sys
.argv
[1:4]
193 die("invalid arguments")
195 if refname
!= "refs/heads/master":
196 die("pushing to a branch other than master is restricted")
198 db
= mysql
.connector
.connect(host
=aur_db_host
, user
=aur_db_user
,
199 passwd
=aur_db_pass
, db
=aur_db_name
,
200 unix_socket
=aur_db_socket
, buffered
=True)
203 # Detect and deny non-fast-forwards.
204 if sha1_old
!= "0000000000000000000000000000000000000000":
205 walker
= repo
.walk(sha1_old
, pygit2
.GIT_SORT_TOPOLOGICAL
)
206 walker
.hide(sha1_new
)
207 if next(walker
, None) != None:
208 cur
.execute("SELECT AccountTypeID FROM Users WHERE UserName = %s ",
210 if cur
.fetchone()[0] == 1:
211 die("denying non-fast-forward (you should pull first)")
213 # Prepare the walker that validates new commits.
214 walker
= repo
.walk(sha1_new
, pygit2
.GIT_SORT_TOPOLOGICAL
)
215 if sha1_old
!= "0000000000000000000000000000000000000000":
216 walker
.hide(sha1_old
)
218 # Validate all new commits.
219 for commit
in walker
:
220 for fname
in ('.SRCINFO', 'PKGBUILD'):
221 if not fname
in commit
.tree
:
222 die_commit("missing {:s}".format(fname
), str(commit
.id))
224 for treeobj
in commit
.tree
:
225 blob
= repo
[treeobj
.id]
227 if isinstance(blob
, pygit2
.Tree
):
228 die_commit("the repository must not contain subdirectories",
231 if not isinstance(blob
, pygit2
.Blob
):
232 die_commit("not a blob object: {:s}".format(treeobj
), str(commit
.id))
234 if blob
.size
> 250000:
235 die_commit("maximum blob size (250kB) exceeded", str(commit
.id))
237 srcinfo_raw
= repo
[commit
.tree
['.SRCINFO'].id].data
.decode()
238 srcinfo_raw
= srcinfo_raw
.split('\n')
239 ecatcher
= aurinfo
.CollectionECatcher()
240 srcinfo
= aurinfo
.ParseAurinfoFromIterable(srcinfo_raw
, ecatcher
)
241 errors
= ecatcher
.Errors()
243 sys
.stderr
.write("error: The following errors occurred "
244 "when parsing .SRCINFO in commit\n")
245 sys
.stderr
.write("error: {:s}:\n".format(str(commit
.id)))
247 sys
.stderr
.write("error: line {:d}: {:s}\n".format(*error
))
250 srcinfo_pkgbase
= srcinfo
._pkgbase
['pkgname']
251 if not re
.match(repo_regex
, srcinfo_pkgbase
):
252 die_commit('invalid pkgbase: {:s}'.format(srcinfo_pkgbase
), str(commit
.id))
254 for pkgname
in srcinfo
.GetPackageNames():
255 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
257 for field
in ('pkgver', 'pkgrel', 'pkgname'):
258 if not field
in pkginfo
:
259 die_commit('missing mandatory field: {:s}'.format(field
), str(commit
.id))
261 if 'epoch' in pkginfo
and not pkginfo
['epoch'].isdigit():
262 die_commit('invalid epoch: {:s}'.format(pkginfo
['epoch']), str(commit
.id))
264 if not re
.match(r
'[a-z0-9][a-z0-9\.+_-]*$', pkginfo
['pkgname']):
265 die_commit('invalid package name: {:s}'.format(pkginfo
['pkgname']),
268 for field
in ('pkgname', 'pkgdesc', 'url'):
269 if field
in pkginfo
and len(pkginfo
[field
]) > 255:
270 die_commit('{:s} field too long: {:s}'.format(field
, pkginfo
[field
]),
273 for field
in ('install', 'changelog'):
274 if field
in pkginfo
and not pkginfo
[field
] in commit
.tree
:
275 die_commit('missing {:s} file: {:s}'.format(field
, pkginfo
[field
]),
278 for field
in extract_arch_fields(pkginfo
, 'source'):
279 fname
= field
['value']
280 if "://" in fname
or "lp:" in fname
:
282 if not fname
in commit
.tree
:
283 die_commit('missing source file: {:s}'.format(fname
), str(commit
.id))
285 # Read .SRCINFO from the HEAD commit.
286 srcinfo_raw
= repo
[repo
[sha1_new
].tree
['.SRCINFO'].id].data
.decode()
287 srcinfo_raw
= srcinfo_raw
.split('\n')
288 srcinfo
= aurinfo
.ParseAurinfoFromIterable(srcinfo_raw
)
290 # Ensure that the package base name matches the repository name.
291 srcinfo_pkgbase
= srcinfo
._pkgbase
['pkgname']
292 if srcinfo_pkgbase
!= pkgbase
:
293 die('invalid pkgbase: {:s}, expected {:s}'.format(srcinfo_pkgbase
, pkgbase
))
295 # Ensure that packages are neither blacklisted nor overwritten.
296 cur
.execute("SELECT ID FROM PackageBases WHERE Name = %s", [pkgbase
])
297 pkgbase_id
= cur
.fetchone()[0] if cur
.rowcount
== 1 else 0
299 cur
.execute("SELECT Name FROM PackageBlacklist")
300 blacklist
= [row
[0] for row
in cur
.fetchall()]
302 for pkgname
in srcinfo
.GetPackageNames():
303 pkginfo
= srcinfo
.GetMergedPackage(pkgname
)
304 pkgname
= pkginfo
['pkgname']
306 if pkgname
in blacklist
and not privileged
:
307 die('package is blacklisted: {:s}'.format(pkginfo
['pkgname']))
309 cur
.execute("SELECT COUNT(*) FROM Packages WHERE Name = %s AND " +
310 "PackageBaseID <> %s", [pkgname
, pkgbase_id
])
311 if cur
.fetchone()[0] > 0:
312 die('cannot overwrite package: {:s}'.format(pkgname
))
314 # Store package base details in the database.
315 save_srcinfo(srcinfo
, db
, cur
, user
)
319 # Create (or update) a branch with the name of the package base for better
321 repo
.create_reference('refs/heads/' + pkgbase
, sha1_new
, True)
323 # Work around a Git bug: The HEAD ref is not updated when using gitnamespaces.
324 # This can be removed once the bug fix is included in Git mainline. See
325 # http://git.661346.n2.nabble.com/PATCH-receive-pack-Create-a-HEAD-ref-for-ref-namespace-td7632149.html
327 repo
.create_reference('refs/namespaces/' + pkgbase
+ '/HEAD', sha1_new
, True)