#Requires -Version 5.0
<#
.Synopsis
stream_report.ps1 outputs a list of all files in the currently selected stream
as a .csv file which is opened in the default application (Excel usually).
It is meant for release streams.
.Description
This is intended as a custom tool for P4V.
It can be easily installed for P4V using Install-P4VUtils.ps1
The output format is a CSV file:
depot, branch, path, filename, revision, changelist, job, action, description, filetype, datechanged, user, JobStatus, JobDescription, JIRAIssue
Assumes 2 level streams depots: //depot/stream_name
.Parameter p4port
The P4PORT to use
.Parameter p4user
The P4USER to use
.Parameter stream
The name of the stream spec to report on.
.Parameter freeze date/time
Date/time (in p4 format without spaces: yyyyy/mm/dd:hh:mm:ss)
.Example
As custom tool in P4V:
Application: powershell
Arguments: -f c:\path\to\stream_report.ps1 $p $u %t $D
Where c:\path\to is the name of directory containing the script
Check option “Run tool in terminal window”
If not debugging, check option “Close window upon completion”
#>
# Doesn't work in older versions of Powershell
# [CmdletBinding()]
param ( [string]$p4port,
[string]$p4user,
[string]$stream)
Function Log([string]$message) {
# Logs output to file and to console
$datetime = Get-Date -format "yyyy\/MM\/dd HH\:mm\:ss"
"$datetime $message" | write-host
}
class P4File {
[string]$DepotFile
[string]$Rev
[string]$Change
[string]$Action
[string]$Filetype
[string]$Desc
[string]$Project
[string]$Branch
[string]$Folder
[string]$Filename
P4File(
[string]$df,
[string]$r,
[string]$chg,
[string]$act,
[string]$t
){
$this.DepotFile = $df
$this.Rev = $r
$this.Change = $chg
$this.Action = $act
$this.Filetype = $t
# Split //depot/stream/folder1/folder2/file
$s = $this.DepotFile.split("/")
$this.Project = $s[2]
$this.Branch = $s[3]
$this.Filename = $s[-1]
$this.Folder = ""
if ($s.length -gt 5) {
$this.Folder = $s[4..($s.length - 2)] -join "/"
}
}
}
# Note that using classes (Powershell 5+) offers sensible variable scoping and function/method returns!
class P4Stream {
[string]$Name
P4Stream([string]$n){
$this.Name = $n
}
[string[]] GetPaths() {
$myargs = $global:p4baseargs + "stream", "-o", $this.Name
Log "p4 $myargs"
$stream_spec = & p4 @myargs
$paths = @()
$inPath = $false
foreach ($line in $stream_spec) {
if ($line -match "^#.*") {
continue
}
if ($line -match "^Paths:") {
$inPath = $true
}
if ($inPath) {
if ($line -match "^$") {
$inPath = $false
} else {
$paths += $line
}
}
}
Log "Paths:"
Log $paths -join "`r`n"
Log "end"
$imports = $paths | select-string "^\s+import"
Log "Imports: $imports"
$import_map = [ordered]@{}
foreach ($line in $imports) {
# Allow for quoted paths (which contain spaces)
Log "PArsing: $line"
switch -regex ($line) {
"^\s+import\s+(\S+)\s+(\S+)$" {
Log "m: $($matches[1]) - $($matches[2])"
$import_map[$matches[1]] = $matches[2]
}
"^\s+import\s+`"(.+?)`"\s+(\S+)$" {
Log "m: $($matches[1]) - $($matches[2])"
$import_map[$matches[1]] = $matches[2]
}
"^\s+import\s+(\S+)\s+`"(.+?)`"$" {
Log "m: $($matches[1]) - $($matches[2])"
$import_map[$matches[1]] = $matches[2]
}
"^\s+import\s+`"(.+?)`"\s+`"(.+?)`"$" {
Log "m: $($matches[1]) - $($matches[2])"
$import_map[$matches[1]] = $matches[2]
}
}
}
$import_sources = @("$($this.Name)/...")
$import_map.keys | ForEach-Object{
Log "$_ : $($import_map[$_])"
$import_sources += $import_map[$_]
}
return $import_sources
}
}
class P4Change {
[string]$Change
[string]$Date
[string]$Client
[string]$User
[string]$Status
[string]$Description
P4Change([string]$chg){
$this.Change = $chg
Log "Creating change: $chg"
$p4args = $global:p4baseargs + "change", "-o", $this.Change
Log "p4 $p4args"
$change_spec = & p4 @p4args
$inDesc = $false
foreach ($line in $change_spec) {
switch -regex ($line) {
"^Change:\s+(\S+)$" {
$this.Change = $matches[1]
}
"^Date:\s+(.*)$" {
$this.Date = $matches[1]
}
"^Client:\s+(\S+)$" {
$this.Client = $matches[1]
}
"^User:\s+(\S+)$" {
$this.User = $matches[1]
}
"^Status:\s+(\S+)$" {
$this.Status = $matches[1]
}
"^Description:" {
$inDesc = $true
continue
}
}
if ($inDesc) {
if ($line -match "^$") {
$inDesc = $false
} else {
if ($line -notmatch "^Description:" ) {
$line = $line -Replace "\t", ""
$line = $line -Replace """", """"""
$this.Description += $line
}
}
}
}
}
}
try {
if ($p4port -eq $null -or $p4port -eq "") {
throw "ERROR: please specify parameter p4port"
}
if ($p4user -eq $null -or $p4user -eq "") {
throw "ERROR: please specify parameter p4user"
}
if ($stream -eq $null -or $path -eq "") {
throw "ERROR: please specify parameter stream"
}
Log "Args p4port:'$p4port' p4user:'$p4user' stream:'$stream'"
$p4baseargs = "-p", $p4port
if ($p4user -ne "default") {
$p4baseargs += "-u", $p4user
}
$changes = @{}
$changenums = @{}
$all_files = @()
$sobj = [P4Stream]::new($stream)
$file_paths = $sobj.GetPaths()
Log "Files paths: $file_paths"
foreach ($path in $file_paths) {
Log "Getting files in $path"
$p4args = $p4baseargs + "-F", "%depotFile%|%depotRev%|%change%|%action%|%type%", "files", "$path"
Log "p4 $p4args"
$files = & p4 @p4args
foreach ($line in $files) {
Log "Processing $line"
$depotfile, $depotrev, $changeno, $action, $type = $line.split("|")
$depotrev = $depotrev -Replace "#", ""
$changeno = $changeno -Replace "change ", ""
[P4File]$f = [P4File]::new($depotfile, $depotrev, $changeno, $action, $type)
$all_files += $f
$changenums[$changeno] = 1
}
}
# Populate change descs
Log "Processing changes"
$changenums.keys | ForEach-Object{
Log "Processing $_"
$changes[$_] = [P4Change]::new($_)
}
$tmpfile = New-Temporaryfile
$outputfile = $tmpfile.FullName -replace ".tmp", ""
$outputfile += ".csv"
# Write CSV header
"depot,branch,path,filename,revision,changelist,action,filetype,datechanged,user,Description,Job,JobStatus,JobDescription,JIRAIssue" | add-content -path $outputfile
foreach ($f in $all_files) {
$chg = $changes[$f.Change]
"$($f.Project),$($f.Branch),$($f.Folder),$($f.Filename),$($f.Rev),$($f.Change),$($f.Action),$($f.Filetype),$($chg.Date),$($chg.user),""$($chg.Description)"",,," | add-content -path $outputfile
}
start "$outputfile"
exit 0
}
Catch
{
write-error $_.Exception
exit 1
}