Scott Hanselman

Prompts and Directories - Even Better Git (and Mercurial) with PowerShell

February 1, '12 Comments [36] Posted in Open Source | PowerShell
Sponsored By

posh-git with custom directoryI love PowerShell and spent years and years working with it since it first came out. I've actually got 15 or so pages of PowerShell posts on this blog going way back.

PowerShell is insanely powerful. I would even go so far as to say it was (is) ahead of its time. PowerShell is totally dynamic and is almost like having JavaScript at the command line, but even more thoughtfully designed. I appreciate folks that love their bash shells and what not, but PowerShell can do some wonderful things.

A long time ago (2009) Mark Embling blogged about a nice prompt with TabExpansion that he'd made to make working with PowerShell and Git (his favorite source control system) nicer. Later, Jeremy Skinner added TabExpansion for common commands. A little while later Keith Dahlby started with their code (with their blessing) and posh-git was born. Expanding even more, Jeremy later created posh-hg (for Mercurial, my favorite source control system).

All of these are currently in use in various forms. Just recently (days ago, even) while I was trying figure out how to get these two separate but similar PowerShell scripts to live together when Keith created a small shared function that makes sharing prompts easier.

I think that Git Bash on Windows needs to go away. It's just not useful to say that a Windows user has to run Bash in order to use Git. PowerShell with Git (or your favorite VCS) is demonstrably better for Windows folks. I also feel that the installation for posh-git, while it uses PsGet (think NuGet for PowerShell, which is INSANELY awesome), just could be easier.

I happened to be tweeting about this and ended up doing a Skype+Join.me 3-way pair programming session with Keith and Paul Betts to explore some ideas on the topic. While a customized prompt is cool, I wouldn't rest until we'd modified "dir/ls" to show source control status. I'm not talking about a PowerShell Provider, I'm talking about extending the View for the result of a dir (get-childitem and a FileInfo) itself.

I want to be able to take a fresh machine and fresh PowerShell installation, invoke one PsGet command and get Git and Hg (and whoever else) integration with PowerShell, a new prompt AND new (yet to be written at this point) directory listing support.

We did some pairing as I sat in a pub and drove while Keith and Paul told me I was a lousy typist. We got a nice prototype working and I went home. After the kids went to sleep I was asking questions on a mailing list and ended up getting an answer from James Brundage, noted PowerShell expert. I'd met James at a Nerd Dinner in Seattle once and gave him a book. He was kind enough to do a screen sharing session with me and refactor my directory spike (and some of posh-git) into a more useful form. It's still a spike, but Keith and I are going to merge all three of them (posh-git, posh-hg and my VCS dir stuff) into one usable and easy to install module. I'm sure we'll both blog about it when it's cleaner. I'm hoping we'll get it all integrated into a single install line:

(new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex
install-module posh-git

Here's the general idea that encapsulates a number of these ideas. Rather than scripts that are plugged into your PowerShell $profile, we'll have a module or two like this.

C:\Users\Scott\Documents\WindowsPowerShell\Modules
$ dir

Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 1/31/2012 10:37 PM Posh-Git
d---- 1/31/2012 10:37 PM Posh-GitDir
d---- 1/31/2012 12:27 AM PsGet

The Posh-Git folder is the Posh-Git source as it is, as a module and imported in your profile like this.

Import-Module Posh-Git
Import-Module Posh-GitDir

Posh-GitDir is my extension module that will change dir/ls/get-childitem and add a Git Status column. I've added extra columns with file information before in PowerShell, except in a cheesy way and I never actually overrode dir directly.

First, we'll make a post-gitdir.Types.ps1xml that adds the new ScriptProperty that pulls details for each file out of a $GitStatus variable that's added each time the prompt is drawn.

<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>System.IO.FileInfo</Name>
<Members>
<ScriptProperty>
<Name>Git</Name>
<GetScriptBlock>
$retVal = ""
if ($GitStatus.Index.Added -contains $this.Name) { $retVal += "+" } `
elseif ($GitStatus.Index.Modified -contains $this.Name) { $retVal += "~" } `
elseif ($GitStatus.Index.Unmerged -contains $this.Name) { $retVal += "!" } `
else { $retVal += " " }

$retVal += " "

if ($GitStatus.Working.Added -contains $this.Name) { $retVal += "+" } `
elseif ($GitStatus.Working.Modified -contains $this.Name) { $retVal += "~" } `
elseif ($GitStatus.Working.Unmerged -contains $this.Name) { $retVal += "!" } `
else { $retVal += " " }

$retVal
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
<Type>
<Name>System.IO.DirectoryInfo</Name>
<Members>
<ScriptProperty>
<Name>Git</Name>
<GetScriptBlock>
""
</GetScriptBlock>
</ScriptProperty>
</Members>
</Type>
</Types>

This adds the Git column to the output as a ScriptProperty, but doesn't change the default view.

$ dir | get-member

TypeName: Get-ChildItem

Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
Create Method System.Void Create(System.Security.AccessControl.DirectorySecurity director...
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
...snip...
BaseName ScriptProperty System.Object BaseName {get=$this.Name;}
Git ScriptProperty System.Object Git {get="";}

I'd have to always select manually, which is tedious.

$ dir | select Name, Git

Name Git
---- ---
CommandType.cs ~
Connection.cs ~
ConnectionExtensions.cs ~
ConnectionManager.cs +
ConnectionScope.cs +
GuidConnectionIdFactory.cs ~

We want to change the dir "view" itself.  We have to copy the default view for a directory at: "C:\Windows\system32\WindowsPowerShell\v1.0\FileSystem.format.ps1xml" and add our new column.

<?xml version="1.0" encoding="utf-16"?>
<Configuration>
<ViewDefinitions>
<View>
<Name>Dir-Git</Name>
<ViewSelectedBy>
<TypeName>Dir-Git</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Git</Label>
<Width>4</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
...snip...
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<Wrap/>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Git</PropertyName>
</TableColumnItem>
...snip...
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>

For the design, I want the Indexed and Working files in two columns, showing Added+, Modified~ and Unmerged! files like this. Deleted files won't show up cause they aren't there.

$ dir #snipped out directories and files for clarity

Mode Git LastWriteTime Length Name
---- --- ------------- ------ ----
d---- 1/31/2012 5:14 PM Configuration
d---- 1/31/2012 5:14 PM Hosting
-a--- + 1/31/2012 5:14 PM 2170 ConnectionManager.cs
-a--- + 1/31/2012 5:14 PM 402 ConnectionScope.cs
-a--- ~ 1/31/2012 5:14 PM 280 GuidConnectionIdFactory.cs
-a--- 1/31/2012 5:14 PM 273 IConnection.cs
-a--- ~ 1/31/2012 5:14 PM 165 IConnectionIdFactory.cs
-a--- + 1/31/2012 5:14 PM 304 IConnectionManager.cs
-a--- 1/31/2012 5:14 PM 118 packages.config
-a--- ~ 1/31/2012 5:14 PM 8296 PersistentConnection.cs
-a--- + 1/31/2012 5:14 PM 1118 PersistentConnectionFactory.cs
-a--- 1/31/2012 5:14 PM 623 PersistentResponse.cs
-a--- ~ 1/31/2012 5:14 PM 1288 SignalCommand.cs
-a--- ~ 1/31/2012 5:14 PM 7386 SignalR.csproj
-a--- 1/31/2012 5:14 PM 23076 TaskAsyncHelper.cs
C:\Users\Scott\Desktop\github\SignalR\SignalR [master +1 ~0 -0 | +10 ~58 -11 !]

We import these modules in our $profile.

Import-Module Posh-Git
Import-Module Posh-GitDir

The posh-git module adds the custom prompt (if you haven't changed yours) with a new function called Write-VcsStatus that is shared between Hg and Git (and any other systems that want to play with us). It only adds the prompt if the user hasn't already customized their prompt. If they have, they'll need to incorporate Write-VcsStatus themselves.

$defaultPromptHash = "HEYStcKFSSj9jrfqnb9f+A=="

$md5 = [Security.Cryptography.MD5]::Create()
$thePrompt = [Text.Encoding]::Unicode.GetBytes((Get-Command prompt | Select-Object -ExpandProperty Definition))
$thePromptHash = [Convert]::ToBase64String($md5.ComputeHash($thePrompt))

if ($thePromptHash -eq $defaultPromptHash) #using the default prompt?
{
#recommend our own
function prompt(){
# Reset color, which can be messed up by Enable-GitColors
$Host.UI.RawUI.ForegroundColor = $GitPromptSettings.DefaultForegroundColor

Write-Host($pwd) -nonewline -foregroundcolor white

Write-VcsStatus

Write-Host ""
return "$ "
}
}
else {
Write-Debug "Make sure your prompt includes a called to Write-VcsStatus!"
}

The craziness that James Brundage came up with to override dir/ls/get-childitem was this. He said he'll do a complete tutorial on his blog with technical details for the generic case.

. ([ScriptBlock]::Create("
function Get-ChildItem {
$([Management.Automation.ProxyCommand]::GetCmdletBindingAttribute((Get-Command Get-ChildItem -CommandType Cmdlet)))
param(
$([Management.Automation.ProxyCommand]::GetParamBlock((Get-Command Get-ChildItem -CommandType Cmdlet)))
)

process {
Microsoft.PowerShell.Management\Get-ChildItem @psBoundParameters |
ForEach-Object {
`$null = `$_.pstypenames.Insert(0, 'Dir-Git')
`$_
}
}
}
"))

Tie it all up with a .psd1 file that has the list of Scripts, Types, Formats and the Module.

@{
ModuleVersion="1.0.0.0"
Author="Scott Hanselman"
Description="Posh-GitDir"
CompanyName="Hanselman and Friends"
RequiredModules="Posh-Git"
ScriptsToProcess="prompt.ps1"
TypesToProcess="posh-gitdir.Types.ps1xml"
FormatsToProcess="posh-gitdir.Format.ps1xml"
ModuleToProcess="posh-gitdir.psm1"
}

To recap, this new module requires the posh-git module, it ands our new "Dir-Git" type, adds the Git ScriptProperty in Types, and shows how to Format it, and overrides get-childitem in the psm1. If you didn't want to override dir proper, maybe you could make a dir-git or dir-hg.

Next steps are for us to integrate them into one module, bring in an inproc library to access the source info (rather than regex'ing the output of git status and hg status) which would speed it up 10x I'm sure, as well as better NuGet support.

In this screenshot you can see posh-git and posh-hg living together. The first directory is a hg repo with a 1 file not in control. The second directory is a git repo with 1 file added in the Index, 10 new added in working, 58 modified, and 11 deleted.

PowerShell with Git and Hg

Keith and Jeremy have done some amazing work. Open Source, baby. I'm looking forward to pairing with them in coming days and buttoning this up. I've been a hardcore Tortoise (Tortoise-Hg, Tortoise-SVN, Tortoise-Git) source control user, but the addition of PowerShell is shaking my faith in a good way. At leat to the point that I think it's worth my spare time to see this through.

Thoughts?

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Wednesday, February 01, 2012 8:44:25 AM UTC
You can of course do most of this in Visual studio using the Nuget powershell thing, except that it doesn't have the ugly nice colors you use. You can then even open and close the solution when checking out as to not confuse VS to much with all the magic.
Wednesday, February 01, 2012 8:55:25 AM UTC
Chrissie1 - What do you mean? You don't get the custom prompts, extended dir or anything like that with the NuGet Powershell.
Wednesday, February 01, 2012 9:17:44 AM UTC
I think I need to add support for PSD1 modules and add Posh-GitDir to psget directory now :). Is it possible to have your module somewhere on github or anywhere else with direct download link (Codeplex for some reason does not provide direct download links).

On github, repository provides download link for entry repository. This is very useful for such modules, because you do not even need to package your module.
Wednesday, February 01, 2012 9:24:29 AM UTC
Ups, sorry missed https://github.com/shanselman/PowerShellHgGit :), so if you do not mind, I will add this module to PsGet.
Wednesday, February 01, 2012 9:26:16 AM UTC
Yes you do have custom prompt in nuget powershell console. Just copy paste this in there and see what happens.

Function Prompt {
"[{0} {1}]PS {2}>" -f (get-date).ToShortDateString(),(get-date).ToLongTimeString(),(get-location)
}

That worked even on nuget 1.5, I think.
Wednesday, February 01, 2012 9:28:21 AM UTC
Mike - We are going to put the dir stuff in Posh-git I think. Not sure how to make hg and git work together AND both be in the DIR module's view. We need to design that.

My module is already in my github. But, give us a few weeks.

Chrissie - yes, I know NuGet has a custom prompt. I wasn't questioning that. I'm talking about the custom source control prompts.
Scott Hanselman
Wednesday, February 01, 2012 9:40:29 AM UTC
>> Not sure how to make hg and git work together AND both be in the DIR module's view. We need to design that.

When you will think about this feature, please, add me to chat.

I was about to solve problem with shared stuff like TabExpansions, Prompts directly in PsGet by adding some sort of optional contact in modules. But if this problem could be solved in PsGet agnostic way, this will be much better, and of course, I want PsGet to support this :).
Wednesday, February 01, 2012 10:32:58 AM UTC
Dear mister Hanselman,

I tried it and it works as advertized even with colors, allthough the colors don't work well on a white background.

Not sure if I can change the backgroundcolor of the nuget PS.

Your servant

Chrissie1 ;-)
Wednesday, February 01, 2012 12:58:54 PM UTC
To me, the dir/ls integration seems to have very limited benefit. The thing I like about hg status is that it shows all changes for the whole repository, not just the current directory.

I like the other things you are doing with this. Maybe I'm missing something but I just don't see the dir/ls stuff being helpful.
Wednesday, February 01, 2012 2:03:03 PM UTC
Chrissie - You can pick your own colors by setting properties on $GitPromptSettings (like in your $PROFILE). I have a post on that in queue that includes a color scheme that works better on white.

Mike - I think the shared TabExpansion/status problem is largely solved (check out recent posh-git commits), but I'm open to suggestions to generalize the solution.

Don - The finished* product will likely make the ls/dir integration optional, so if you don't find it useful you can turn it off.

* As if OSS is ever finished. Even log4net.
Wednesday, February 01, 2012 2:12:30 PM UTC
I tried out posh-hg a while back. And, while I liked it, I found that when I had it auto-importing in my profile, it killed my Package Manager Console tab completion in VS. Have you experienced this behavior?
Wednesday, February 01, 2012 2:21:44 PM UTC
I use both posh-git and posh-hg all the time. This is going to rock!
Wednesday, February 01, 2012 3:01:18 PM UTC
I must disagree that the git-bash shell isn't useful. It's much more powerful than the regular DOS shell and can do everything I've ever needed it to do.
Wednesday, February 01, 2012 4:12:45 PM UTC
@Mike, tangentially: I have high hopes for PsGet, but it needs two substantial things before it can become a CPAN for PowerShell:

① PAUSE: Something better than throwing the module at an admin or checking out the entire catalog to add something.

② Content: The list is quite small (due in part to ①, perhaps). Scott blogging about it will help significantly. Should the modules in the PowerShell Community Extensions be broken into more granular pieces and incorporated into PsGet? Should Codeplex offer some kind of streamlined experience to add a project to PsGet or their own directory?

Even further off-topic, and super pedantic: The alias for Install-Module should be ismo (rather than ipmo). According to MSDN: Cmdlet Verbs, is is the preferred alias prefix for "Install", and ip for "Import".
Wednesday, February 01, 2012 4:30:31 PM UTC
I agree bash is light years ahead of DOS. I'm saying it's not light years ahead of PowerShell. ;)
Scott Hanselman
Wednesday, February 01, 2012 4:50:54 PM UTC
I think that Git Bash on Windows needs to go away. It's just not useful. PowerShell with Git (or your favorite VCS) is demonstrably better for Windows folks. (emphasis added)

Scott, I would love for you to demonstrate this. I'm not sure I'm buying it. I think you should support this claim by doing a video with a Bash guru and a PS guru, comparing and contrasting the advantages/disadvantages.
Wednesday, February 01, 2012 5:13:22 PM UTC
Mike - Note I said "on windows," but yes, a head would be fun! I'm referring specifically to the idea that Git users on Windows *need* bash to use Git effectively. I think I wasn't clear.
Scott Hanselman
Wednesday, February 01, 2012 6:33:44 PM UTC
Right on, Scott. I have been irritated with Git Bash on Windows since I started using it. Can't say I'm a ninja at powershell but it would feel a lot more like home that our existing options. And if you're planning to deploy it via PsGet that would be awesome.
Wednesday, February 01, 2012 6:36:30 PM UTC
Thanks Keith. I'll try that tomorrow.
Wednesday, February 01, 2012 6:37:23 PM UTC
Wow... you have "servants" :)
Wednesday, February 01, 2012 7:20:39 PM UTC
I can not get this posh-gitdir to work properly. I do not have the Git-Dir command available or the Git column available in the Get-ChildItem command.
Wednesday, February 01, 2012 7:29:14 PM UTC
Andres - Ah, that's because I mentioned it's a spike and it's early. Unless you know PowerShell you'll likely have trouble until we make an installed.

If you REALLY want to get it working you need to:

- Make C:\Users\YOU\Documents\WindowsPowerShell\Modules
- Make a posh-git and posh-gitdir folder in Modules\ spelling is crucial
- Put posh-git in one and my posh-gitdir spike in the other
- Add Import Posh-Git and Import Posh-GitDir in your PowerShell profile which his located at $profile.
Wednesday, February 01, 2012 7:38:21 PM UTC
Thanks, It works now. I had the posh-git and posh-gitdir module folders in my projects directory (where I had cloned them). Once I placed them in the 'Modules' directory all began to work. Thanks again.
Wednesday, February 01, 2012 8:31:58 PM UTC
I used to go to uni with Mark Embling ... it is a small world.
Wednesday, February 01, 2012 9:40:37 PM UTC
Brianary

>> ① PAUSE: Something better than throwing the module at an admin or checking out the entire catalog to add something.

Agree. Directory.xml should be modified and then pulled to main repository and this is not something very user friendly. But that was most easy to implement.


>> ② Content: The list is quite small (due in part to ①, perhaps). Scott blogging about it will help significantly. Should the modules in the PowerShell Community Extensions be broken into more granular pieces and incorporated into PsGet? Should Codeplex offer some kind of streamlined experience to add a project to PsGet or their own directory?

Codeplex does not provide direct download link :(. I was thinking about hosting copies of modules somewhere else. But this kills the beauty of the frictionless expirience of getting latest version of the module.

>> Even further off-topic, and super pedantic: The alias for Install-Module should be ismo (rather than ipmo). According to MSDN: Cmdlet Verbs, is is the preferred alias prefix for "Install", and ip for "Import".

You are right. I think for now I will keep them both, and later I will retire ipmo.

Seems I have job for this weekend :). Thanks for your feedback.
Wednesday, February 01, 2012 10:07:05 PM UTC
Keith

>> I think the shared TabExpansion/status problem is largely solved (check out recent posh-git commits), but I'm open to suggestions to generalize the solution.

Wow, looks awesome, some feedback https://github.com/dahlbyk/posh-git/commit/36d61ca85cd6e241b8cd8068eb45699dc40101e7#commitcomment-924238
Wednesday, February 01, 2012 10:09:40 PM UTC
mastering git bash has the advantage of at least learning some bash that is useful on a *nix system. Why limit oneself to Windows only
Wednesday, February 01, 2012 10:56:31 PM UTC
Bash is far more useful on windows when using the rxvt shell from Cygwin. There is a version that doesn't require an X11 server and it runs great. Even has resize with mouse and middle click to paste ;)
Wednesday, February 01, 2012 11:28:11 PM UTC
Out of curiosity, is there anyway to get color-coded output from git in the ISE? It works fine in the console, but not in the ISE.

I like PS as a scripting language, but every time I decide I want to start using it as my standard console, stuff like the above pops up. PS is a decent language, but the shell environments seem to be generations behind the un*x equivalents.
David
Thursday, February 02, 2012 2:04:01 PM UTC
SmartGit, anyone? - http://www.syntevo.com/smartgit/index.html It's great for 99% of git daily tasks - for the rest there is the command line :) (e.g. interactive rebase, etc)

Thursday, February 02, 2012 7:02:21 PM UTC
I'm still learning PowerShell (and git), but I've done some updates to posh-svn for my personal use and also done a little integration with posh-hg and posh-git. It works for me, mostly, but as I'm no guru, I KNOW there's room for improvement.

https://github.com/somewhatabstract/posh-svn
Monday, February 06, 2012 10:28:09 PM UTC
This sounds awesome. I can't wait to see the result!
Friday, February 10, 2012 7:03:16 AM UTC
I just found studioshell (showed up in my RSS-feed) And just look at the videos. They even put that information in html and grids and stuff.

http://studioshell.codeplex.com/

Would have been even cooler if it was on Github of course ;-)
Wednesday, February 29, 2012 9:49:29 PM UTC
Hey Scott, This is great. I love Posh-Git and this makes it even better.

Thought you might like to know about a small quirk. If you change directories using the wrong case, the directory changes, but the Wip column does not populate.

Cheers Tim.
Sunday, December 23, 2012 3:37:17 AM UTC
Funny that you say "PowerShell is totally dynamic and is almost like having JavaScript at the command line".
We do in fact have JavaScript at the command line. Heard of Node.js?
Monday, February 04, 2013 10:23:54 AM UTC
For Powershell 3.0 users, installing psget is even easier :

iwr http://psget.net/GetPsGet.ps1 | iex

Et voila !
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.