Skip to content

Instantly share code, notes, and snippets.

@jayrm
Last active September 16, 2017 18:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jayrm/b1fbc8199ce0d38858fe48076fc090d2 to your computer and use it in GitHub Desktop.
Save jayrm/b1fbc8199ce0d38858fe48076fc090d2 to your computer and use it in GitHub Desktop.
`git dirs' custom command, get the status of multiple local repositories and submodules recursively, written in freebasic
'' --------------------------------------------------------
'' git-dirs.bas
'' --------------------------------------------------------
'' get status of multiple git directories, recursively
'' copy to {GITDIR}/libexec/git-core/git-dirs[.exe]
'' use `git dirs -h' for usage or see help at bottom of this file
type GIT_DIRS_OPTIONS
recursive as boolean = false
alldirs as boolean = false
showhelp as boolean = false
incolor as boolean = true
sorted as boolean = false
fetch as boolean = false
status as boolean = false
system_dirs as boolean = false
verbose as boolean = false
end type
type GIT_STATUS_RESULT
detached as boolean '' current branch is detached head
currbranch as string '' name of the current branch
staged as boolean '' staged changes to commit (use commit)
modified as boolean '' modified files to stage (use add)
noupstream as boolean '' no upstream remote configured
ahead as integer '' current branch is ahead (use push)
behind as integer '' current branch is behind (use pull)
end type
dim shared opts as GIT_DIRS_OPTIONS
'' --------------------------------------------------------
'' DIRECTORY
'' - read contents of directory
'' - use fb's dir() function
'' - store all directories and filenames except . & ..
'' --------------------------------------------------------
#include once "dir.bi"
type FILE_INFO
filename as string
attrib as integer
end type
type DIRECTORY
private:
m_files(any) as FILE_INFO
m_num_files as integer
m_max_files as integer
declare sub addfile( byref f as const string, byval attrib as integer )
declare sub quicksort( byval lo as integer, byval hi as integer )
public:
declare constructor()
declare destructor()
declare sub Clear()
declare function ReadDirectory( byref path as const string, byval system as boolean ) as boolean
declare property Count() as integer
declare property FileName( byval index as integer ) as string
declare property FileAttrib( byval index as integer ) as integer
declare sub Sort()
end type
''
private sub DIRECTORY.addfile( byref f as const string, byval attrib as integer )
if( m_num_files = m_max_files ) then
m_max_files *= 2
redim preserve m_files( 1 to m_max_files )
end if
m_num_files += 1
with m_files(m_num_files)
.filename = f
.attrib = attrib
end with
end sub
''
sub DIRECTORY.Clear()
for i as integer = 1 to m_num_files
m_files(i).filename = ""
next
m_num_files = 0
end sub
''
constructor DIRECTORY()
m_num_files = 0
m_max_files = 16
redim m_files( 1 to m_max_files ) as FILE_INFO
end constructor
''
destructor DIRECTORY()
this.Clear()
erase m_files
end destructor
''
function DIRECTORY.ReadDirectory( byref path as const string, byval system as boolean ) as boolean
this.Clear()
dim attrib as integer
dim d as string
d = dir( path & "*.*", &hff, attrib )
while( len(d) > 0 )
select case d
case "."
case ".."
case else
'' only include system files if system=true
if( (system = true) orelse ((attrib and fbSystem) = 0) ) then
addfile( d, attrib )
end if
end select
d = dir( attrib )
wend
function = cbool( m_num_files > 0 )
end function
''
property DIRECTORY.Count() as integer
property = m_num_files
end property
''
property DIRECTORY.FileName( byval index as integer ) as string
if( index >= 1 andalso index <= m_num_files ) then
property = m_files(index).filename
else
property = ""
end if
end property
''
property DIRECTORY.FileAttrib( byval index as integer ) as integer
if( index >= 1 andalso index <= m_num_files ) then
property = m_files(index).attrib
else
property = 0
end if
end property
''
private sub DIRECTORY.quicksort( byval lo as integer, byval hi as integer )
if( lo < hi ) then
dim pivot as string = m_files(hi).filename
'' parition
dim i as integer = lo - 1
for j as integer = lo to hi - 1
if m_files(j).filename < pivot then
i += 1
swap m_files(i), m_files(j)
end if
next
if( m_files(hi).filename < m_files(i+1).filename ) then
swap m_files(i+1), m_files(hi)
end if
dim p as integer = i+1
'' sort each side of the partition
quicksort( lo, p-1 )
quicksort( p+1, hi )
end if
end sub
''
sub DIRECTORY.Sort()
this.quicksort( 1, m_num_files )
end sub
'' --------------------------------------------------------
'' COLOURS
'' --------------------------------------------------------
dim shared last_color as integer = 7
const COLOR_IS_GIT_DIR = 7
const COLOR_IS_GIT_SUB = 6
const COLOR_IS_NORMAL_DIR = 4
''
sub git_dir_save_color()
if( opts.incolor ) then
last_color = color()
end if
end sub
''
sub git_dir_load_color()
if( opts.incolor ) then
color LOWORD(last_color)
end if
end sub
''
sub git_dir_color( byval clr as integer )
if( opts.incolor ) then
color clr
end if
end sub
'' --------------------------------------------------------
'' STATUS
'' --------------------------------------------------------
''
sub verbose( byref cmd as const string )
if( opts.verbose ) then
print cmd
end if
end sub
''
function git_dir_get_status( byref path as const string ) as GIT_STATUS_RESULT
'' make multiple `git' command shell calls and
'' check results to determine the status of each
'' git directory
dim h as integer = freefile
dim cmd as string
dim ret as GIT_STATUS_RESULT
'' ----------
'' get the name of the current branch and test for detached head
'' git [-C path] symbolic-ref HEAD --short --quiet --
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " symbolic-ref HEAD --short --quiet --"
verbose( "CMD: " & cmd )
if( open pipe ( cmd for input as #h ) = 0 ) then
dim x as string
line input #h, x
close #h
ret.currbranch = x
end if
ret.detached = cbool( ret.currbranch = "" )
'' ----------
'' if the head is detached, then don't bother going any further
'' we will leave up to the user to investigate more
if( ret.detached = true ) then
goto abort
end if
'' ----------
'' test for staged changes not yet committed
'' returns 0 = no, 1 = yes
'' git [-C path] diff-index --quiet --cached HEAD --
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " diff-index --quiet --cached HEAD --"
verbose( "CMD: " & cmd )
ret.staged = cbool( shell( cmd ) )
'' ----------
'' test for changes not yet staged
'' returns 0 = no, 1 = yes
'' git [-C path] diff-files --quiet
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " diff-files --quiet"
verbose( "CMD: " & cmd )
ret.modified = cbool( shell( cmd ) )
'' ----------
'' test for untracked files
'' git [-C path] ls-files --others
'' !!! not implemented
'' ----------
'' test for untracked and unignored
'' git [-C path] ls-files --exclude-standard --others
'' !!! not implemented
'' ----------
'' test for upstream remote tracking for current branch
'' git [-C path] branch -r --color=never
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " branch -r --color=never"
verbose( "CMD: " & cmd )
ret.noupstream = true
if( open pipe ( cmd for input as #h ) = 0 ) then
dim x as string
while eof(h) = false
line input #h, x
if( len(x) > len(ret.currbranch) ) then
if( right( x, len(ret.currbranch) + 1 ) = "/" & ret.currbranch ) then
ret.noupstream = false
exit while
end if
end if
wend
close #h
end if
'' ----------
'' if no upstream remote for current branch, then abort status checks
if( ret.noupstream ) then
goto abort
end if
'' ----------
'' determine number of commits ahead/behind
'' git [-C path] rev-list --left-right --count HEAD...@{u}
'' returns N1<tab>N2
'' N1 = number of commits current branch is AHEAD of tracking branch
'' N2 = number of commets current branch is BEHIND tracking branch
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " rev-list --left-right --count HEAD...@{u}"
verbose( "CMD: " & cmd )
if( open pipe ( cmd for input as #h ) = 0 ) then
dim x as string
line input #h, x
close #h
dim i as integer = instr( x, chr(9) )
if( i > 0 ) then
ret.ahead = val(left(x,i-1))
ret.behind = val(mid(x,i+1))
else
ret.ahead = val(x)
ret.behind = 0
end if
end if
abort:
function = ret
end function
''
function git_dir_ReadDirectory _
( _
byref basepath as const string, _
byref path as const string, _
byval follow as const boolean = false _
) as boolean
dim lst as DIRECTORY
lst.ReadDirectory( basepath + path, opts.system_dirs )
if( opts.sorted ) then
lst.Sort()
end if
dim is_git_dir as boolean = false
dim is_git_sub as boolean = false
for i as integer = 1 to lst.count
if( lst.filename(i) = ".git" ) then
if(( lst.fileattrib(i) and fbDirectory ) <> 0 ) then
is_git_dir = true
else
is_git_sub = true
end if
exit for
end if
next
dim c as integer = 0
dim msg as string = ""
if( is_git_dir ) then
if( path = "" ) then
msg = "<current directory>"
else
msg = path
end if
c = COLOR_IS_GIT_DIR
elseif( is_git_sub ) then
if( path = "" ) then
msg = "<current directory>"
else
msg = path
end if
c = COLOR_IS_GIT_SUB
else
if( opts.alldirs ) then
msg = path
c = COLOR_IS_NORMAL_DIR
end if
end if
if( is_git_dir orelse is_git_sub orelse opts.alldirs ) then
git_dir_save_color()
git_dir_color( c )
print chr(9) + msg
git_dir_load_color()
end if
if( is_git_dir orelse is_git_sub ) then
if( opts.fetch ) then
dim cmd as string
cmd = "git"
if( path > "" ) then cmd &= " -C """ & path & """"
cmd &= " fetch --all --tags"
verbose( cmd )
shell( cmd )
end if
if( opts.status ) then
dim s as GIT_STATUS_RESULT = git_dir_get_status( path )
if( s.detached ) then
print chr(9) & chr(9) & "detached head (use ""git commit"", ""git reset"", or ""git checkout"", to fix)"
end if
dim prefix as string = chr(9) & chr(9) & "[" & s.currbranch & "]: "
if( s.modified ) then
print prefix & "files modified (use ""git add"")"
end if
if( s.staged ) then
print prefix & "files staged (use ""git commit"")"
end if
if( s.noupstream ) then
print prefix & "no upstream branch (use ""git push --set-upstream origin " & s.currbranch & """)"
end if
if( s.ahead <> 0 ) then
print prefix & s.ahead & " commit(s) ahead (use ""git push"")"
end if
if( s.behind <> 0 ) then
print prefix & s.behind & " commit(s) behind (use ""git pull"")"
end if
end if
end if
if( follow ) then
for i as integer = 1 to lst.count
if( (lst.fileattrib(i) and fbDirectory) <> 0 ) then
if( lcase(lst.filename(i)) <> ".git" ) then
dim p as string = path + lst.filename(i) + "/"
git_dir_ReadDirectory( basepath, p, opts.recursive )
end if
end if
next
end if
function = true
end function
'' --------------------------------------------------------
'' GIT DIRS -- MAIN
'' --------------------------------------------------------
dim i as integer = 1
while command(i) <> ""
select case lcase(command(i))
case "-h", "-help"
opts.showhelp = true
case "-a", "--all"
opts.alldirs = true
case "--color", "--color=y", "--color=yes"
opts.incolor = true
case "--color=n", "--color=no", "--color=never"
opts.incolor = false
case "-r", "--recursive"
opts.recursive = true
case "-s", "--sorted"
opts.sorted = true
case "--status"
opts.status = true
case "--fetch"
opts.fetch = true
case "--system"
opts.system_dirs = true
case "--verbose", "-v"
opts.verbose = true
case else
print "arg '" & command(i) & "' ignored"
end select
i += 1
wend
if( opts.showhelp ) then
print "usage: git dirs [options]"
print
print " -a, --all all directories (except system)"
print " --color[=yes|no] color formatting"
print " -r, --recursive recursive"
print " -s, --sorted sorted"
print " --status query status of each git dir"
print " --fetch perform a 'fetch --all --tags' at each git dir"
print " --system include system directories"
print " -v, --verbose be more verbose"
print
end
end if
git_dir_ReadDirectory( "./", "", true )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment