jimmy keen

on .NET, C# and unit testing

Bitbucket activity analysis (and) logging

October 18, 2016 | tags: bitbucket javascript jquery

I do majority of my home projects on Bitbucket. You know how it displays recent user activity? No? Probably because you did not even notice:

Bitbucket dashboard

That is correct, Bitbucket does not display any activity at all.

BAAL

I wanted to have an easy, visual overview of where my Bitbucket work is allocated. So I developed a simple tool displaying project-based Bitbucket activity, like this:

    Code-name BAAL. Through combined power of 250 lines of css, html and js BAAL reads Bitbucket user RSS feed and builds project activity histogram, as seen above. No REST APIs, no auth, no complex DOM manipulation. Additionally, you can toggle issue breakdown when you click on project histogram.

    Want to see your Bitbucket activity? Download baal.html and change bitbucketRssUrl variable to your user URL (which can be found at https://bitbucket.org/dashboard/repositories).

    Reset a file with Git command line (Windows)

    August 22, 2016 | tags: git windows powershell xargs

    I use bunch of Git commands and aliases during my daily work. Although I am quite happy with my current setup, one thing has been lacking for a while – a simple command to undo changes made to file. Even though Git offers a default solution, I never really liked it. Recently, I finally managed to create a viable replacement.

    To stage or to unstage

    Before we begin, it is important to understand what happens when you modify a file tracked by Git. First, it is marked as unstaged which means this file has changed compared to its previous version but it is not commit ready yet. When you run git status it is mentioned explicitly:

    > git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   CsharpKatas/Xml/Creation.cs
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    Also note that commiting at this point produces no commit as there is nothing to include in it:

    > git commit -m "Fix"
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes not staged for commit:
            modified:   CsharpKatas/Xml/Creation.cs
    
    no changes added to commit
    

    We have been already hinted as to what to do next which is git add <file>. Most of the times you are probably using git add -A to include all unstaged files in soon-to-be commit.

    Once a file is added with git add it becomes staged – or in other words – commit ready. This file will be included in the next commit you create. As expected, status command recognizes this fact:

    > git add -A
    > git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
            modified:   CsharpKatas/Xml/Creation.cs
    

    Quick recap:

    • when you modify tracked file it becomes unstaged, noticed by Git but not yet ready for commit
    • when you use git add unstaged file(s) become staged and will be included in next commit

    Commands mentioned above constitute to vast majority of what Git shell user executes daily. My usual workflow (and I can imagine many other people’s) is:

    1. Modify files
    2. Verify: git status
    3. Commit: git add -A then git commit -m "Fixed"
    4. Push git push origin HEAD

    These commands are so common that I use very short aliases for them: git s, git c "Fixed" (this also does git add -A) and git up. This makes interaction with shell very smooth.

    Sins of automation

    Unfortunately, this approach has one major flaw. It is a bulk-operation thanks to git add -A + git commit combo. Imagine you modified 10 files but changes in one of them need to be discarded. Documentation suggest to use checkout command:

    git checkout -- <path-to-file>

    This is quite some typing. However:

    • good news is, Git allows tab-cycling through unstaged files
    • bad news is, since checkout command is also used to checkout branches, tab-cycling includes all branches
    • good news is you can use wildcard/auto-completion
    • bad news is, as soon as you hide checkout -- behind alias, tab-cycling is gone

    What I would like instead is simple undo command:

    git undo <file-name>

    Putting together all the pieces

    There is a bunch of Git commands and tools to help us here. What needs to be done is:

    1. With git diff we can get unstaged files paths (using --name-only switch)
    2. Filter them to the one matching pattern using unix grep
    3. Feed this path to git checkout -- using unix xargs

    Last two commands don’t occur naturally in Windows environment but luckily Windows Git distribution includes them. Just make sure you have Git bin directory in PATH.

    Alright, assume that our working directory contains two modifed files, like this:

    > git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   CsharpKatas/Xml/Creation.cs
            modified:   CsharpKatas/Xml/Querying.cs
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    Let’s try and match Quotation.cs with git diff and grep:

    > git diff --name-only
    CsharpKatas/Xml/Creation.cs
    CsharpKatas/Xml/Querying.cs
    > git diff --name-only | grep Query
    CsharpKatas/Xml/Querying.cs
    

    Almost there. All we need to do left is somehow pass this to git checkout --. Regular piping does not work and we need a special command, the xargs. What it does is simply pass its arguments to a command of choice. The final command looks like this:

    git diff --name-only | grep Query | xargs git checkout --

    Running this we got:

    > git diff --name-only | grep Query | xargs git checkout --
    ' did not match any file(s) known to git..cs
    

    Come again?

    Story behind xargs

    Judging by the strange output, could it be that Windows’ carriage return and line feed (aka new line) is causing problems? Let’s investigate what xargs does with --verbose switch:

    > git diff --name-only | grep Query | xargs --verbose git checkout --
     it checkout -- CsharpKatas/Xml/Querying.cs
    ' did not match any file(s) known to git..cs
    

    Yep, it seems we are on the right track with \r symbol obfuscating output. New line confusion is a common issue with xargs which uses whitespaces and newlines to separate arguments. Unix commands deal with this problem with -print0 switch which changes default argument separator to \0, end-of-string character. On Windows we do not have such option.

    To mitigate this problem we could simply say that both \r\n are argument separator for xargs. Let’s see where this takes us:

    > git diff --name-only | grep Query | xargs -d \r\n --verbose git checkout --
    git checkout -- CsharpKatas/Xml/Querying.cs
    
    error: pathspec '
    ' did not match any file(s) known to git.
    

    Closer. It looks like xargs seems to think we have two arguments, path and the newline. But we only want to pass one argument to git checkout invocation and xargs has a switch to do just that:

    > git diff --name-only | grep Query | xargs --verbose -d \r\n -n 1 git checkout --
    git checkout -- CsharpKatas/Xml/Querying.cs
    git checkout --
    
    error: pathspec '
    ' did not match any file(s) known to git.
    

    What happened here? The -n 1 switch changed single invocation with two arguments (path and newline) to two invocations with single argument each (one with path, one with newline). And this means we managed to successfully undo changes to one file. While second invocation is definitely excessive, I was not able to get rid of it.

    Note that all this was tested with Powershell 5. To see what version you run, use $PSVersionTable.PSVersion. For example, Powershell 3 seems to work with \r\n differently and requires slightly modified combination:

    git diff --name-only | grep Query | tr -s '\r\n' '\0' | xargs -d '\0' -n 1 git checkout --

    In brief, it is a simple emulation of unix approach where end-of-string character is used to separate arguments.

    Wrapping up

    There is one more thing we need to do – enclose this command as git alias. And this causes yet another issues. The behavior regarding \r\n changes once again with inline function. Powershell 3 version of the command seems to be the most robust and this is the one I use. Remembering few Powershell tricks we arrive at:

    > git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   CsharpKatas/Xml/Querying.cs
    
    > git config --global alias.undo "!f() { git diff --name-only | grep `$1 | tr -s '\r\n' '\0' | xargs -d '\0' -n 1 git checkout -- ; }; f"
    >
    > git undo Query
    > git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    nothing to commit, working directory clean
    

    We have an alias. In a similar fashion we can create unstage command to degrade once staged files (using diff with --staged switch and git reset HEAD --).

    Using Git with Powershell on Windows

    November 10, 2015 | tags: git windows powershell posh-git

    This will be a quick guide on how to set up Powershell with Git on Windows.

    Essentials

    1. Powershell
    2. Git for Windows
    3. Git credentials store

    Download and install files above. Make sure Git bin directory is added to your PATH environmental variable. Credentials store are not really required but I’m pretty sure you do not want to type your login details with every push or pull, right?

    Aliases

    Just as with any console application, Git offers a way to streamline your work with aliases. Rather than typing

    > git add -A

    > git commit -m "Added config file"

    You can simply type

    > git c "Added config file"

    This of course goes further than that considering we have Powershell at our disposal. For example, you can easily create alias like this:

    > git config --global alias.logby "!f() { git log --author='`$1'; }; f"

    > git logby Jimmy

    We create f function and execute it straight away. You can read more about this approach at “Git alias with parameters”. When setting such alias from powershell itself, make sure to escape $-variables with ` (grave accent symbol), otherwise powershell will try to insert variables which are not there.

    You can see some more aliases here.

    The Posh

    A neat little extension offering git commands completion and making your Powershell look like this:

    posh-git powershell highlighting

    Posh-git can be installed from powershell:

    > (new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex

    > Install-Module posh-git

    If your powershell compains that your execution policy is restricted, make sure to un-restrict it with:

    > Set-ExecutionPolicy RemoteSigned

    It’s great we can see current branch name. It’s also great that we can see whether there were any modifications. But I’m not a huge fan of having all the details of changed files, untracked, stashed and so forth. I would rather have this:

    posh-git powershell simplified highlighting

    A simple indicator telling me what branch I am on and what’s the status (* - pending changes, - synced with origin, - ahead of origin).

    Behavior described above can be achieved if we modify posh-git code, namely GitPrompt.ps1 file. You can find this file location in one of your powershell modules directories (check $env:PSModulePath variable). You can use the files from my repository:

    And that’s it. Enjoy your brand new Git + Powershell experience!