lazybox: suppport repo worker

repo_lfs and repo_unshallow
dev
cfig 1 year ago
parent 43317d4f92
commit f18f99a009
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -1,6 +1,7 @@
package cfig.lazybox package cfig.lazybox
import cfig.lazybox.staging.DiffCI import cfig.lazybox.staging.DiffCI
import cfig.lazybox.staging.RepoWorker
import cfig.lazybox.sysinfo.BootChart import cfig.lazybox.sysinfo.BootChart
import cfig.lazybox.sysinfo.CpuInfo import cfig.lazybox.sysinfo.CpuInfo
import cfig.lazybox.sysinfo.Pidstat import cfig.lazybox.sysinfo.Pidstat
@ -26,9 +27,11 @@ fun main(args: Array<String>) {
println("cpuinfo : get cpu info from /sys/devices/system/cpu/") println("cpuinfo : get cpu info from /sys/devices/system/cpu/")
println("sysinfo : get overall system info from Android") println("sysinfo : get overall system info from Android")
println("\nIncubating usage:") println("\nIncubating usage:")
println("apps : get apk file list from Android") println("apps : get apk file list from Android")
println("dmainfo : parse /d/dma_buf/bufinfo") println("dmainfo : parse /d/dma_buf/bufinfo")
println("diffci : find changelist files from CI server based on date and time ranges") println("diffci : find changelist files from CI server based on date and time ranges")
println("repo_lfs : pull LFS files from Git repositories managed by 'repo'")
println("repo_unshallow: unshallow Git repositories managed by 'repo'")
exitProcess(0) exitProcess(0)
} }
if (args[0] == "cpuinfo") { if (args[0] == "cpuinfo") {
@ -102,4 +105,10 @@ fun main(args: Array<String>) {
if (args[0] == "diffci") { if (args[0] == "diffci") {
DiffCI().run(args.drop(1).toTypedArray()) DiffCI().run(args.drop(1).toTypedArray())
} }
if (args[0] == "repo_lfs") {
RepoWorker().lfsPullRepo(args.drop(1).toTypedArray())
}
if (args[0] == "repo_unshallow") {
RepoWorker().unshallowRepo(args.drop(1).toTypedArray())
}
} }

@ -1,8 +1,12 @@
package cfig.lazybox.staging package cfig.lazybox.staging
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URI import java.net.URI
import java.net.URL
import java.net.http.HttpClient import java.net.http.HttpClient
import java.net.http.HttpRequest import java.net.http.HttpRequest
import java.net.http.HttpResponse import java.net.http.HttpResponse
@ -20,7 +24,7 @@ class DiffCI {
private val dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd") private val dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd")
private val dateTimeFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm") private val dateTimeFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm")
private val buildIdRegex = ".*/(\\d{12})/changelist".toRegex() private val buildIdRegex = ".*/(\\d{12})/changelist".toRegex()
private val log = LoggerFactory.getLogger(DiffCI::class.java) private val log = LoggerFactory.getLogger(DiffCI::class.simpleName)
fun run(args: Array<String>) { fun run(args: Array<String>) {
if (baseUrl == FAKE_URL) { if (baseUrl == FAKE_URL) {
@ -85,7 +89,7 @@ class DiffCI {
val allUrls = mutableListOf<String>() val allUrls = mutableListOf<String>()
var currentDate = startDate var currentDate = startDate
while (currentDate <= endDate) { while (currentDate <= endDate) {
log.info("\n----- Processing Date: $currentDate -----") log.info("----- Processing Date: $currentDate -----")
val dailyUrls = finder.findChangelistUrlsForDate(currentDate) val dailyUrls = finder.findChangelistUrlsForDate(currentDate)
allUrls.addAll(dailyUrls) allUrls.addAll(dailyUrls)
currentDate = currentDate.plusDays(1) currentDate = currentDate.plusDays(1)
@ -109,7 +113,7 @@ class DiffCI {
val allUrls = mutableListOf<String>() val allUrls = mutableListOf<String>()
var currentDate = startDateTime.toLocalDate() var currentDate = startDateTime.toLocalDate()
while (currentDate <= endDateTime.toLocalDate()) { while (currentDate <= endDateTime.toLocalDate()) {
log.info("\n----- Processing Date: $currentDate -----") log.info("----- Processing Date: $currentDate -----")
val dailyUrls = finder.findChangelistUrlsForDate(currentDate) val dailyUrls = finder.findChangelistUrlsForDate(currentDate)
val filteredUrls = dailyUrls.filter { url -> val filteredUrls = dailyUrls.filter { url ->
@ -130,19 +134,85 @@ class DiffCI {
private fun printUsageAndExit() { private fun printUsageAndExit() {
log.info("Error: Invalid number of arguments.") log.info("Error: Invalid number of arguments.")
log.info("\nUsage:") log.info("Usage:")
log.info(" Single Date: --args='diffci <yyyymmdd>'") log.info(" Single Date: --args='diffci <yyyymmdd>'")
log.info(" Date Range: --args='diffci <start_date> <end_date>'") log.info(" Date Range: --args='diffci <start_date> <end_date>'")
log.info(" Time Range: --args='diffci <start_datetime> <end_datetime>'") log.info(" Time Range: --args='diffci <start_datetime> <end_datetime>'")
log.info("\nExamples:") log.info("Examples:")
log.info(" --args='diffci 20250628'") log.info(" --args='diffci 20250628'")
log.info(" --args='diffci 20250627 20250628'") log.info(" --args='diffci 20250627 20250628'")
log.info(" --args='diffci 202506281000 202506281430'") log.info(" --args='diffci 202506281000 202506281430'")
exitProcess(1) exitProcess(1)
} }
fun fetchChangeList(urlsToFetch: List<String>) {
val OUTPUT_FILENAME = "merged_content.txt"
if (urlsToFetch.isEmpty()) {
return
}
val outputFile = File(OUTPUT_FILENAME)
log.info("Starting to fetch content from ${urlsToFetch.size} URLs ...")
outputFile.bufferedWriter().use { writer ->
// Iterate over each URL string in the list.
urlsToFetch.forEachIndexed { index, urlString ->
log.info("(${index + 1}/${urlsToFetch.size}) Fetching: $urlString")
try {
// Create a URL object from the string.
val url = URL(urlString)
val connection = (url.openConnection() as HttpURLConnection).also {
it.requestMethod = "GET"
it.connectTimeout = 5000 // 5 seconds
it.readTimeout = 5000 // 5 seconds
}
val responseCode = connection.responseCode
// Write a header for this URL's content in the output file.
writer.write("--- START: Content from $urlString ---")
writer.newLine()
// Check for a successful HTTP response (2xx status codes).
if (responseCode in 200..299) {
connection.inputStream.bufferedReader().use { reader ->
// Read the content line by line and write to the file.
reader.forEachLine { line ->
writer.write(line)
writer.newLine()
}
}
} else {
val errorMessage = "Received non-OK response code: $responseCode"
log.info("Warning: $errorMessage for $urlString")
writer.write("! FAILED: $errorMessage")
writer.newLine()
}
} catch (e: MalformedURLException) {
val errorMessage = "Invalid URL format"
log.error("Error for '$urlString': $errorMessage. Skipping.")
writer.write("! FAILED: $errorMessage")
writer.newLine()
} catch (e: IOException) {
val errorMessage = "Error reading data (I/O problem): ${e.message}"
log.info("Error for '$urlString': $errorMessage. Skipping.")
writer.write("! FAILED: $errorMessage")
writer.newLine()
} catch (e: Exception) {
val errorMessage = "An unexpected error occurred: ${e.message}"
log.error("Error for '$urlString': $errorMessage. Skipping.")
writer.write("! FAILED: $errorMessage")
writer.newLine()
} finally {
// Write a footer and add extra newlines for separation.
writer.write("--- END: Content from $urlString ---")
writer.newLine()
writer.newLine()
}
}
}
log.info("Process complete. '${outputFile.absolutePath}'.")
}
private fun printResults(urls: List<String>, isTimeRange: Boolean = false) { private fun printResults(urls: List<String>, isTimeRange: Boolean = false) {
log.info("\n--- Results ---") log.info("--- Results ---")
if (urls.isNotEmpty()) { if (urls.isNotEmpty()) {
log.info("Successfully found ${urls.size} changelist files:") log.info("Successfully found ${urls.size} changelist files:")
urls.forEach { log.info(it) } urls.forEach { log.info(it) }
@ -151,6 +221,7 @@ class DiffCI {
log.info("No changelist files were found for the specified $rangeType.") log.info("No changelist files were found for the specified $rangeType.")
} }
log.info("---------------") log.info("---------------")
fetchChangeList(urls)
} }
private fun parseDate(dateStr: String): LocalDate? { private fun parseDate(dateStr: String): LocalDate? {

@ -0,0 +1,181 @@
package cfig.lazybox.staging
import java.io.File
import java.io.ByteArrayOutputStream
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.ExecuteWatchdog
import org.apache.commons.exec.PumpStreamHandler
import org.slf4j.LoggerFactory
class RepoWorker {
private val log = LoggerFactory.getLogger(RepoWorker::class.simpleName)
fun lfsPullRepo(args: Array<String>) {
val startPath = args.firstOrNull() ?: "."
val dirList = getMatchRepositories(startPath, ::isLfsEnabled, "LFS Enabled")
log.warn("Found ${dirList.size} repositories with LFS enabled.")
dirList.forEach { repoDir ->
val relativePath = repoDir.toRelativeString(File(startPath).canonicalFile).ifEmpty { "." }
log.info("Pulling [$relativePath]...")
pullLfsContent(repoDir)
}
log.info("✨ Scan and pull complete.")
}
fun unshallowRepo(args: Array<String>) {
val startPath = args.firstOrNull() ?: "."
val dirList = getMatchRepositories(startPath, ::isShallowClone, "Shallow Clone")
log.warn("Found ${dirList.size} shallow repositories.")
dirList.forEach { repoDir ->
val relativePath = repoDir.toRelativeString(File(startPath).canonicalFile).ifEmpty { "." }
log.info("Unshallowing [$relativePath]...")
unshallowGit(repoDir)
}
log.info("✨ Scan and unshallow complete.")
}
private fun getMatchRepositories(
startPath: String,
checker: (File) -> Boolean,
checkerName: String = "LFS Enabled"
): List<File> {
val ret = mutableListOf<File>()
val startDir = File(startPath).canonicalFile
log.info("🔍 Finding Git repositories using 'repo forall' in: ${startDir.absolutePath}")
val gitRepositories = findGitRepositoriesWithRepo(startDir)
if (gitRepositories.isEmpty()) {
log.info("No Git repositories found. Make sure you are running this in a directory managed by 'repo'.")
return listOf<File>()
}
log.info("✅ Found ${gitRepositories.size} Git repositories. Now checking for $checkerName status ...")
gitRepositories.forEach { repoDir ->
val relativePath = repoDir.toRelativeString(startDir).ifEmpty { "." }
if (checker(repoDir)) {
log.info("Checking [$relativePath]...")
log.info(" -> ✅ $checkerName")
ret.add(repoDir)
} else {
//log.info("Checking [$relativePath]...")
//log.info(" -> 🔴 LFS Not Enabled.")
}
}
return ret
}
private fun pullLfsContent(repoDir: File) {
try {
val commandLine = CommandLine("git").also {
it.addArgument("lfs")
it.addArgument("pull")
}
DefaultExecutor().also {
//it.watchdog = ExecuteWatchdog(600000)
it.streamHandler = PumpStreamHandler(System.out, System.err)
it.workingDirectory = repoDir
it.setExitValue(0)
it.execute(commandLine)
}
log.info(" -> ✅ 'git lfs pull' completed successfully.")
} catch (e: Exception) {
log.error(" -> ❌ 'git lfs pull' failed for ${repoDir.name}: ${e.message}")
}
}
private fun unshallowGit(repoDir: File) {
try {
val commandLine = CommandLine("git").also {
it.addArgument("fetch")
it.addArgument("--progress")
it.addArgument("--unshallow")
}
DefaultExecutor().also {
//it.watchdog = ExecuteWatchdog(180000)
it.streamHandler = PumpStreamHandler(System.out, System.err)
it.workingDirectory = repoDir
it.setExitValue(0)
it.execute(commandLine)
}
log.info(" -> ✅ 'git fetch --unshallow' completed successfully.")
} catch (e: Exception) {
log.error(" -> ❌ 'git fetch --unshallow' failed for ${repoDir.name}: ${e.message}")
}
}
private fun findGitRepositoriesWithRepo(workingDir: File): List<File> {
return try {
val commandLine = CommandLine("repo").also {
it.addArgument("forall")
it.addArgument("-c")
it.addArgument("pwd")
}
val outputStream = ByteArrayOutputStream()
DefaultExecutor().also {
it.watchdog = ExecuteWatchdog(60000) // 60 seconds
it.streamHandler = PumpStreamHandler(outputStream)
it.workingDirectory = workingDir
it.setExitValue(0)
it.execute(commandLine)
}
val output = outputStream.toString().trim()
if (output.isEmpty()) {
emptyList()
} else {
output.split(System.lineSeparator())
.map { it.trim() }
.filter { it.isNotEmpty() }
.map { File(it) }
}
} catch (e: Exception) {
log.error("Error executing 'repo forall': ${e.message}")
log.error("Please ensure the 'repo' tool is installed and you are in a valid repo workspace.")
emptyList()
}
}
private fun isLfsEnabled(repoDir: File): Boolean {
return try {
val commandLine = CommandLine("git").also {
it.addArgument("lfs")
it.addArgument("track")
}
val outputStream = ByteArrayOutputStream()
DefaultExecutor().also {
it.watchdog = ExecuteWatchdog(5000)
it.streamHandler = PumpStreamHandler(outputStream)
it.workingDirectory = repoDir
it.setExitValue(0)
it.execute(commandLine)
}
val output = outputStream.toString()
val lines = output.lines().filter { it.isNotBlank() }
return lines.size > 1
} catch (e: Exception) {
false
}
}
fun isShallowClone(repoDir: File): Boolean {
if (!repoDir.isDirectory) {
return false
}
return try {
val commandLine = CommandLine("git").also {
it.addArgument("rev-parse")
it.addArgument("--is-shallow-repository")
}
val outputStream = ByteArrayOutputStream()
val executor = DefaultExecutor().also {
it.watchdog = ExecuteWatchdog(5000)
it.streamHandler = PumpStreamHandler(outputStream)
it.workingDirectory = repoDir
it.setExitValue(0)
}
executor.execute(commandLine)
val output = outputStream.toString().trim()
return output.equals("true", ignoreCase = true)
} catch (e: Exception) {
false
}
}
}
Loading…
Cancel
Save