]>
git.proxmox.com Git - mirror_linux-firmware.git/blob - contrib/process_linux_firmware.py
668e35c0eb0600fcd4cde19c560a4b32a8dd8542
14 from datetime
import date
17 URL
= "https://lore.kernel.org/linux-firmware/new.atom"
20 class ContentType(Enum
):
28 "are available in the Git repository at": ContentType
.PULL_REQUEST
,
29 "diff --git": ContentType
.PATCH
,
30 "Signed-off-by:": ContentType
.PATCH
,
34 def classify_content(content
):
35 # load content into the email library
36 msg
= email
.message_from_string(content
)
39 subject
= msg
["Subject"]
41 return ContentType
.REPLY
42 if "PATCH" in subject
:
43 return ContentType
.PATCH
45 for part
in msg
.walk():
46 if part
.get_content_type() == "text/plain":
48 body
= part
.get_payload(decode
=True).decode("utf-8")
49 for key
in content_types
.keys():
51 return content_types
[key
]
53 except UnicodeDecodeError as e
:
54 logging
.warning("Failed to decode email: %s, treating as SPAM" % e
)
56 return ContentType
.SPAM
60 with urllib
.request
.urlopen(url
) as response
:
61 return response
.read().decode("utf-8")
65 logging
.debug("Running {}".format(cmd
))
66 output
= subprocess
.check_output(cmd
, stderr
=subprocess
.STDOUT
)
70 def reply_email(content
, branch
):
71 if "SMTP_USER" in os
.environ
:
72 user
= os
.environ
["SMTP_USER"]
73 if "SMTP_PASS" in os
.environ
:
74 password
= os
.environ
["SMTP_PASS"]
75 if "SMTP_SERVER" in os
.environ
:
76 server
= os
.environ
["SMTP_SERVER"]
77 if "SMTP_PORT" in os
.environ
:
78 port
= os
.environ
["SMTP_PORT"]
79 if not user
or not password
or not server
or not port
:
80 logging
.debug("Missing SMTP configuration, not sending email")
83 reply
= email
.message
.EmailMessage()
85 orig
= email
.message_from_string(content
)
86 reply
["To"] = ", ".join(
87 email
.utils
.formataddr(t
)
88 for t
in email
.utils
.getaddresses(
89 orig
.get_all("from", []) + orig
.get_all("to", []) + orig
.get_all("cc", [])
93 reply
["From"] = "linux-firmware@kernel.org"
94 reply
["Subject"] = "Re: {}".format(orig
["Subject"])
95 reply
["In-Reply-To"] = orig
["Message-Id"]
96 reply
["References"] = orig
["Message-Id"]
97 reply
["Thread-Topic"] = orig
["Thread-Topic"]
98 reply
["Thread-Index"] = orig
["Thread-Index"]
101 "Your request has been forwarded by the Linux Firmware Kernel robot.\n"
102 "Please follow up at https://gitlab.com/kernel-firmware/linux-firmware/-/merge_requests to ensure it gets merged\n"
103 "Your request is '{}'".format(branch
)
105 reply
.set_content(content
)
107 mailserver
= smtplib
.SMTP(server
, port
)
109 mailserver
.starttls()
111 mailserver
.login(user
, password
)
112 mailserver
.sendmail(reply
["From"], reply
["To"], reply
.as_string())
116 def create_pr(remote
, branch
):
124 "merge_request.create",
126 "merge_request.remove_source_branch",
128 "merge_request.target=main",
130 "merge_request.title={}".format(branch
),
135 def refresh_branch():
136 quiet_cmd(["git", "checkout", "main"])
137 quiet_cmd(["git", "pull"])
140 def delete_branch(branch
):
141 quiet_cmd(["git", "checkout", "main"])
142 quiet_cmd(["git", "branch", "-D", branch
])
145 def process_pr(mbox
, num
, remote
):
146 branch
= "robot/pr-{}-{}".format(num
, int(time
.time()))
148 # manual fixup for PRs from drm firmware repo
149 if "git@gitlab.freedesktop.org:drm/firmware.git" in mbox
:
151 "git@gitlab.freedesktop.org:drm/firmware.git",
152 "https://gitlab.freedesktop.org/drm/firmware.git",
155 cmd
= ["b4", "--debug", "pr", "-b", branch
, "-"]
156 logging
.debug("Running {}".format(cmd
))
157 p
= subprocess
.Popen(
158 cmd
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
160 stdout
, stderr
= p
.communicate(mbox
.encode("utf-8"))
161 for line
in stdout
.splitlines():
162 logging
.debug(line
.decode("utf-8"))
163 for line
in stderr
.splitlines():
164 logging
.debug(line
.decode("utf-8"))
166 # determine if it worked (we can't tell unfortunately by return code)
167 cmd
= ["git", "branch", "--list", branch
]
168 logging
.debug("Running {}".format(cmd
))
169 result
= subprocess
.check_output(cmd
)
172 for line
in result
.splitlines():
173 logging
.debug(line
.decode("utf-8"))
174 logging
.info("Forwarding PR for {}".format(branch
))
176 create_pr(remote
, branch
)
177 delete_branch(branch
)
182 def process_patch(mbox
, num
, remote
):
183 # create a new branch for the patch
184 branch
= "robot/patch-{}-{}".format(num
, int(time
.time()))
185 cmd
= ["git", "checkout", "-b", branch
]
190 logging
.debug("Running {}".format(cmd
))
191 p
= subprocess
.Popen(
192 cmd
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
194 stdout
, stderr
= p
.communicate(mbox
.encode("utf-8"))
195 for line
in stdout
.splitlines():
196 logging
.debug(line
.decode("utf-8"))
197 for line
in stderr
.splitlines():
198 logging
.debug(line
.decode("utf-8"))
199 if p
.returncode
!= 0:
200 quiet_cmd(["git", "am", "--abort"])
202 logging
.info("Opening PR for {}".format(branch
))
204 create_pr(remote
, branch
)
206 delete_branch(branch
)
207 if p
.returncode
== 0:
212 def update_database(conn
, url
):
216 """CREATE TABLE IF NOT EXISTS firmware (url text, processed integer default 0, spam integer default 0)"""
220 if os
.path
.exists(url
):
221 with
open(url
, "r") as f
:
225 logging
.info("Fetching {}".format(url
))
226 atom
= fetch_url(url
)
228 # Parse the atom and extract the URLs
229 feed
= feedparser
.parse(atom
)
231 # Insert the URLs into the database (oldest first)
232 feed
["entries"].reverse()
233 for entry
in feed
["entries"]:
234 c
.execute("SELECT url FROM firmware WHERE url = ?", (entry
.link
,))
237 c
.execute("INSERT INTO firmware VALUES (?, ?, ?)", (entry
.link
, 0, 0))
239 # Commit the changes and close the connection
243 def process_database(conn
, remote
):
246 # get all unprocessed urls that aren't spam
247 c
.execute("SELECT url FROM firmware WHERE processed = 0 AND spam = 0")
254 logging
.info("No new entries")
259 # loop over all unprocessed urls
262 msg
= "Processing ({}%)".format(round(num
/ len(rows
) * 100))
263 print(msg
, end
="\r", flush
=True)
265 url
= "{}raw".format(row
[0])
266 logging
.debug("Processing {}".format(url
))
267 mbox
= fetch_url(url
)
268 classification
= classify_content(mbox
)
270 if classification
== ContentType
.PATCH
:
271 logging
.debug("Processing patch ({})".format(row
[0]))
272 branch
= process_patch(mbox
, num
, remote
)
274 if classification
== ContentType
.PULL_REQUEST
:
275 logging
.debug("Processing PR ({})".format(row
[0]))
276 branch
= process_pr(mbox
, num
, remote
)
278 if classification
== ContentType
.SPAM
:
279 logging
.debug("Marking spam ({})".format(row
[0]))
280 c
.execute("UPDATE firmware SET spam = 1 WHERE url = ?", (row
[0],))
282 if classification
== ContentType
.REPLY
:
283 logging
.debug("Ignoring reply ({})".format(row
[0]))
285 c
.execute("UPDATE firmware SET processed = 1 WHERE url = ?", (row
[0],))
287 print(" " * len(msg
), end
="\r", flush
=True)
294 reply_email(mbox
, branch
)
296 logging
.info("Finished processing {} new entries".format(len(rows
)))
299 if __name__
== "__main__":
300 parser
= argparse
.ArgumentParser(description
="Process linux-firmware mailing list")
301 parser
.add_argument("--url", default
=URL
, help="URL to get ATOM feed from")
304 default
=os
.path
.join("contrib", "linux_firmware.db"),
305 help="sqlite database to store entries in",
307 parser
.add_argument("--dry", action
="store_true", help="Don't open pull requests")
309 "--debug", action
="store_true", help="Enable debug logging to console"
311 parser
.add_argument("--remote", default
="origin", help="Remote to push to")
313 "--refresh-cycle", default
=0, help="How frequently to run (in minutes)"
315 args
= parser
.parse_args()
317 if not os
.path
.exists("WHENCE"):
319 "Please run this script from the root of the linux-firmware repository"
325 "{prefix}-{date}.{suffix}".format(
326 prefix
="linux_firmware", suffix
="txt", date
=date
.today()
330 format
="%(asctime)s %(levelname)s:\t%(message)s",
336 # set a format which is simpler for console use
337 console
= logging
.StreamHandler()
339 console
.setLevel(logging
.DEBUG
)
341 console
.setLevel(logging
.INFO
)
342 formatter
= logging
.Formatter("%(asctime)s : %(levelname)s : %(message)s")
343 console
.setFormatter(formatter
)
344 logging
.getLogger("").addHandler(console
)
347 conn
= sqlite3
.connect(args
.database
)
348 # update the database
349 update_database(conn
, args
.url
)
356 # process the database
357 process_database(conn
, remote
)
361 if args
.refresh_cycle
:
362 logging
.info("Sleeping for {} minutes".format(args
.refresh_cycle
))
363 time
.sleep(int(args
.refresh_cycle
) * 60)