]>
git.proxmox.com Git - mirror_linux-firmware.git/blob - contrib/process_linux_firmware.py
12 from datetime
import datetime
, timedelta
, date
16 URL
= "https://lore.kernel.org/linux-firmware/new.atom"
19 class ContentType(Enum
):
27 "diff --git": ContentType
.PATCH
,
28 "Signed-off-by:": ContentType
.PATCH
,
29 "are available in the Git repository at": ContentType
.PULL_REQUEST
,
33 def classify_content(content
):
34 # load content into the email library
35 msg
= email
.message_from_string(content
)
38 subject
= msg
["Subject"]
40 return ContentType
.REPLY
41 if "PATCH" in subject
:
42 return ContentType
.PATCH
44 for part
in msg
.walk():
45 if part
.get_content_type() == "text/plain":
46 body
= part
.get_payload(decode
=True).decode("utf-8")
47 for key
in content_types
.keys():
49 return content_types
[key
]
51 return ContentType
.SPAM
55 with urllib
.request
.urlopen(url
) as response
:
56 return response
.read().decode("utf-8")
60 logging
.debug("Running {}".format(cmd
))
61 output
= subprocess
.check_output(cmd
, stderr
=subprocess
.STDOUT
)
65 def create_pr(remote
, branch
):
73 "merge_request.create",
75 "merge_request.remove_source_branch",
77 "merge_request.target=main",
79 "merge_request.title={}".format(branch
),
85 quiet_cmd(["git", "checkout", "main"])
86 quiet_cmd(["git", "pull"])
89 def delete_branch(branch
):
90 quiet_cmd(["git", "checkout", "main"])
91 quiet_cmd(["git", "branch", "-D", branch
])
94 def process_pr(url
, num
, remote
):
95 branch
= "robot/pr-{}-{}".format(num
, int(time
.time()))
96 cmd
= ["b4", "pr", "-b", branch
, url
]
99 except subprocess
.CalledProcessError
:
100 logging
.warning("Failed to apply PR")
103 # determine if it worked (we can't tell unfortunately by return code)
104 cmd
= ["git", "branch", "--list", branch
]
105 logging
.debug("Running {}".format(cmd
))
106 result
= subprocess
.check_output(cmd
)
109 logging
.info("Forwarding PR for {}".format(branch
))
111 create_pr(remote
, branch
)
112 delete_branch(branch
)
115 def process_patch(mbox
, num
, remote
):
116 # create a new branch for the patch
117 branch
= "robot/patch-{}-{}".format(num
, int(time
.time()))
118 cmd
= ["git", "checkout", "-b", branch
]
122 cmd
= ["b4", "shazam", "-m", "-"]
123 logging
.debug("Running {}".format(cmd
))
124 p
= subprocess
.Popen(
125 cmd
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
127 stdout
, stderr
= p
.communicate(mbox
.encode("utf-8"))
128 for line
in stdout
.splitlines():
129 logging
.debug(line
.decode("utf-8"))
130 for line
in stderr
.splitlines():
131 logging
.debug(line
.decode("utf-8"))
132 if p
.returncode
!= 0:
133 quiet_cmd(["git", "am", "--abort"])
135 logging
.info("Opening PR for {}".format(branch
))
137 create_pr(remote
, branch
)
139 delete_branch(branch
)
142 def update_database(conn
, url
):
146 """CREATE TABLE IF NOT EXISTS firmware (url text, processed integer default 0, spam integer default 0)"""
150 if os
.path
.exists(url
):
151 with
open(url
, "r") as f
:
155 logging
.info("Fetching {}".format(url
))
156 atom
= fetch_url(url
)
158 # Parse the atom and extract the URLs
159 feed
= feedparser
.parse(atom
)
161 # Insert the URLs into the database (oldest first)
162 feed
["entries"].reverse()
163 for entry
in feed
["entries"]:
164 c
.execute("SELECT url FROM firmware WHERE url = ?", (entry
.link
,))
167 c
.execute("INSERT INTO firmware VALUES (?, ?, ?)", (entry
.link
, 0, 0))
169 # Commit the changes and close the connection
173 def process_database(conn
, remote
):
176 # get all unprocessed urls that aren't spam
177 c
.execute("SELECT url FROM firmware WHERE processed = 0 AND spam = 0")
184 logging
.info("No new entries")
189 # loop over all unprocessed urls
192 msg
= "Processing ({}%)".format(round(num
/ len(rows
) * 100))
193 print(msg
, end
="\r", flush
=True)
195 url
= "{}raw".format(row
[0])
196 logging
.debug("Processing {}".format(url
))
197 mbox
= fetch_url(url
)
198 classification
= classify_content(mbox
)
200 if classification
== ContentType
.PATCH
:
201 logging
.debug("Processing patch ({})".format(row
[0]))
202 process_patch(mbox
, num
, remote
)
204 if classification
== ContentType
.PULL_REQUEST
:
205 logging
.debug("Processing PR ({})".format(row
[0]))
206 process_pr(row
[0], num
, remote
)
208 if classification
== ContentType
.SPAM
:
209 logging
.debug("Marking spam ({})".format(row
[0]))
210 c
.execute("UPDATE firmware SET spam = 1 WHERE url = ?", (row
[0],))
212 if classification
== ContentType
.REPLY
:
213 logging
.debug("Ignoring reply ({})".format(row
[0]))
215 c
.execute("UPDATE firmware SET processed = 1 WHERE url = ?", (row
[0],))
217 print(" " * len(msg
), end
="\r", flush
=True)
221 logging
.info("Finished processing {} new entries".format(len(rows
)))
224 if __name__
== "__main__":
225 parser
= argparse
.ArgumentParser(description
="Process linux-firmware mailing list")
226 parser
.add_argument("--url", default
=URL
, help="URL to get ATOM feed from")
229 default
=os
.path
.join("contrib", "linux_firmware.db"),
230 help="sqlite database to store entries in",
232 parser
.add_argument("--dry", action
="store_true", help="Don't open pull requests")
234 "--debug", action
="store_true", help="Enable debug logging to console"
236 parser
.add_argument("--remote", default
="origin", help="Remote to push to")
238 "--refresh-cycle", default
=0, help="How frequently to run (in minutes)"
240 args
= parser
.parse_args()
242 if not os
.path
.exists("WHENCE"):
244 "Please run this script from the root of the linux-firmware repository"
250 "{prefix}-{date}.{suffix}".format(
251 prefix
="linux_firmware", suffix
="txt", date
=date
.today()
255 format
="%(asctime)s %(levelname)s:\t%(message)s",
261 # set a format which is simpler for console use
262 console
= logging
.StreamHandler()
264 console
.setLevel(logging
.DEBUG
)
266 console
.setLevel(logging
.INFO
)
267 formatter
= logging
.Formatter("%(asctime)s : %(levelname)s : %(message)s")
268 console
.setFormatter(formatter
)
269 logging
.getLogger("").addHandler(console
)
272 conn
= sqlite3
.connect(args
.database
)
273 # update the database
274 update_database(conn
, args
.url
)
281 # process the database
282 process_database(conn
, remote
)
286 if args
.refresh_cycle
:
287 logging
.info("Sleeping for {} minutes".format(args
.refresh_cycle
))
288 time
.sleep(int(args
.refresh_cycle
) * 60)