Wednesday, July 25, 2012

Git-Tfs Setup And Development Workflow

I finally got around to testing the git-tfs tool. I originally looked at this several months ago and didn't have time to dive into it and WOW was I missing out.

Those That Came Before Me

There are some excellent blogs out there which have plenty of information on this tool, except they all seem to miss the a major issue that none of them address. I will cover them below and the highlights of the installation process.

The Setup

To set it up, just clone the git-tfs GitHub repository. If you use GitHub for Windows, just click the "Clone in Windows" button.

To build the solution, make sure to use the Visual Studio 2010 Command Line, or at least make sure the Visual Studio environment variables are set.
msbuild GitTfs.sln /p:Configuration=Vs2010_Debug
It appears that there is a hidden step that should immediately follow this, copying the output to the Git\bin directory. From the git-tfs directory where the solution file exists, run the following (update accordingly).
copy /Y GitTfs.Vs2010\bin\Debug\*.dll "C:\Program Files (x86)\Git\bin"
copy /Y GitTfs.Vs2010\bin\Debug\*.exe "C:\Program Files (x86)\Git\bin"
Verify that the Git\bin directory is in your path. Open a git bash shell and run:
git tfs help
If the command is found, it is ready to go. At this point a celebration may be appropriate. There is still plenty of awesomeness to come.

The Simple Development Workflow

The workflow for this is actually pretty simple. Git-tfs is only needed in 3 use cases:
  • Clone the project
  • Pulling updates from TFS
  • Pushing files to TFS
Once the project has been cloned, standard git best practices apply. All check-ins to the git repository are stored with the reposiotry.

Getting The Project Files

There are a couple ways to get files out of TFS. You can clone the entire TFS project or just part of it. Performing a quick-clone will generate a git repository with only the latest code (no history). Using clone will generate a git repository with full history. NOTE: in all examples below, omitting <Local Location> will generate the repository in the current directory.

The following is an example of the commands required:
git tfs clone http://vstfs:8080 $/Team.Project <Local Location>

git tfs quick-clone http://vstfs:8080 $/Team.Project <Local Location>
The following are examples of generating a partial repository.
git tfs clone http://vstfs:8080 $/Team.Project/ <Local Location>

git tfs quick-clone http://vstfs:8080 $/Team.Project/ <Local Location>
This can be very useful for instances where a TFS project has multiple root folders like branches, tags, and trunk. It doesn't make a lot of sense to clone the tags unless you have to have it especially if the trunk is needed.

From this point forward, all local changes can be maintained with Git Bash or GitHub for Windows until the repository needs to be updated or changes checked in.

Updating The Repository From TFS

Updating is straight forward. All merging is accomplished with Git's merging functionality and tools.
git tfs pull

Pushing to TFS

Once the changes are complete, use the following to check in the changes. Checking-in to TFS does a rebase, in that it only checks in the latest version and the other check-ins in the git repository are left as is but not persisted in TFS.
git tfs checkin -m "Add a great comment"

That

Friday, July 20, 2012

Visual Studio Package Manager Console Add Default Functions

After posting my useful one liners, I started thinking about ways to bake the functions into VS without having to copy and paste from my OneNote all the time. I remembered that powershell had profile scripts that run at the start just like most linux shells.

In order to add a profile to the Package Manager Console, create a file called NuGet_profile.ps1 in the C:\Users\\Documents\WindowsPowerShell directory.

# Copy the NuGet_profile.ps1 file to (the folder may not exist):
#
# C:\Users\<username>\Documents\WindowsPowerShell\NuGet_profile.ps1

function DebugWeb() {
 $dte.Debugger.LocalProcesses | ? { ($_.Name  -match ".*(w3wp|WebDev.WebSe.*)\.exe$") }  | % { $_.Attach() };
}

If you have project specific functions you can create separate scripts for each function and save them to the solution directory. Since the console's default directory is the solution folder, all of the scripts are at your fingertips.

As a bonus, adding the setVSVars function that I introduced in [[Powershell Unlock File In TFS Using Visual Studio Command Line Vars]], with a simple change, I can add functionality that will restore focus to the current window. I highlighted the additions and left in a couple lines (11, 12) which show other ways to access windows inside Visual Studio.

function set-VSVars() {
 $activeWindow = $dte.ActiveWindow;
 #Set Visual Studio Command Prompt environment variables`n
 pushd "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC";
 cmd /c "vcvarsall.bat&set" | ? { $_ -match "^(?<Name>[^=]+)=(?<Value>.+)$" } | % {
     Set-Item -Force -Path "ENV:\$($matches['Name'])" -Value $matches["Value"];
 }
 popd;
 Write-Host "Visual Studio 2010 Command Prompt variables set." -ForegroundColor Yellow;
 
 #$dte.Windows | ? { $_.Caption -eq "Package Manager Console" } | % { $_.Activate(); }
 #$dte.Windows.Item("Package Manager Console").Activate();
 $activeWindow.Activate();
}

This is useful if you need access to Visual Studio Command Line functions.

Saturday, July 14, 2012

Prevent Text Box Selection And Automatically Select Different Control

I ran into an interesting issue where I needed to allow a user to select a contact in a custom SharePoint 2010 application. Preventing the selection of the text box is not as straight forward as one might hope, especially handling cross browser. Since SharePoint has the concept of a People Picker, I decided that I could leverage that pattern to increase application performance. The requirement had several parts:
  1. The result needed to only display the users name
  2. The ID of the contact needed to be saved
  3. The label needs to be able to send the value back to the server, but prevent people from editing the label
The first and third points are solved by using a text box instead of a label. The ID is saved by hiding ID text box so that it is posted back with the rest of the data. We make the solution slightly more elegant by treating the text box like modern browsers handle the file input control; clicking the text box opens the dialog. There is nothing that is specific to ASP.NET and can be easily converted to standard HTML.
<asp:TextBox ID="txtContact" onfocus="focusCtrl('btnContact', event);" runat="server"></asp:TextBox>
<asp:TextBox ID="txtContactID" style="display: none;" runat="server"></asp:TextBox>
<asp:Button ID="btnContact" OnClientClick="return contactPicker('txtContactID', 'txtContact');" Text="Select" CausesValidation="false" runat="server"></asp:Button>
<asp:RequiredFieldValidator ID="rfvContact" ControlToValidate="txtContact" ErrorMessage="Contact is required" ValidationGroup="Required" Display="Dynamic" InitialValue="" CssClass="ms-error" EnableClientScript="false" runat="server">
    <img alt="Validation Error" title="Contact is required" src="/_layouts/images/EXCLAIM.GIF" />
</asp:RequiredFieldValidator>
<script type="text/javascript">
     $(function () { focusCtrl('btnContact'); });
</script>
A couple important notes about the focusCtrl function, the code above assumes that the specified control will be initially selected, but the same function is used for the text box focus event with different outcomes. The initial run without the event won't pass an event into the function which won't trigger the control click event. Once the focus event has fired off an event is passed in and causes the controls click event to fire. I included a version of the contact picker function to show how we are populating controls on the callback.
function focusCtrl(ctlID, e) {
    var ctl = $('input[id$=\'' + ctlID + '\']');
    e = e || window.event;
    var t = (e) ? e.target || e.srcElement : null;
    if (ctl.length > 0) {
        ctl.focus();
        if (t != null) {
            ctl.click();
        }
    } else if (e || window.event) {
        $(t).blur();
        //Focus next textbox
        //$(t).next("input:not(input[type='submit']):visible").focus();
    }
    return false;
}

function contactPicker(txtID, txtLabel) {
    var options = {
        url: '../Contacts/ContactPicker.aspx',
        title: 'Contact Picker',
        allowMaximize: true,
        showClose: true,
        showMaximized: false,
        dialogReturnValueCallback: function (dialogResult, returnValue) {
                                        if (dialogResult == SP.UI.DialogResult.OK) {
                                            var ret = unescape(returnValue),
                                                name = ret.split(";#")[1];
                                            $("input[id$='" + txtName + "']").val(name);
                                            $("input[id$='" + txtID + "']").val(ret);
                                        }
                                    }
    };
    SP.UI.ModalDialog.showModalDialog(options);
    return false;
}

Tuesday, July 10, 2012

Condencing Long If Statements Where A Value Is One of Many Values

Every once in a while I come across a situation where I need to run a section of code if it is one of many different values. There are a several solutions to this situation.

The most common is the basic long form statement. This is well and good but takes a while to write and is pretty verbose. In my opinion, it is actually a bit harder to read.
$('.format-number').each(function () {
    var t = $(this);
    if (t[0].nodeName === "INPUT" || t[0].nodeName === "SELECT") {
        t.val(SomeStringFormatFunc(t.val()));
    }
    else {
        t.text($.fn.autoNumeric.Format(t.attr('id'), t.text()));
    }
});
Let's focus on the if statement. After some reworking the statement, we can use an array and the indexOf function to determine the same. It is a little shorter.
if (["INPUT", "SELECT"].indexOf(t[0].nodeName) >= 0) {
The next example exploits some, sadly rarely used, JavaScript functionality and makes it shorter yet. Using an JSON object, dictionary look up, and if statement evaluation knowledge, the following uses all of these to achieve the same result.
if (({ "INPUT": 1, "SELECT": 1 })[t[0].nodeName]) {
A potential benefit of both refactor attempts is that the array and JSON object can be refactored if those need to be reused in other places. Alternatively, if it needs to be configurable, abstracting the array or JSON object solutions cater to that need.

Friday, July 6, 2012

Powershell Unlock File In TFS Using Visual Studio Command Line Vars

I recently found a situation where someone checked out a file and locked it. That severely impedes my ability to make changes to that file. So I set out to remove the lock using Powershell, while I didn't get the native Powershell functionality I normally like, it still achieves the solution in a usable script.

The solution required that I use tf.exe, not an issue, but it is not in windows path by default. It is available if you use the Visual Studio Command Line, which is just a regular command line which runs a batch file. I thought it might be useful to have the ability to turn Powershell into a Visual Studio Command Line. I found a blog post, Replace Visual Studio Command Prompt with Powershell, that works, but I updated it to work with Visual Studio 2010 and more fully use the power of Powershell.

Set Visual Studio Command Prompt environment variables
#Set Visual Studio Command Prompt environment variables
pushd "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC";
cmd /c "vcvarsall.bat&set" | ? { $_ -match "^(?<Name>[^=]+)=(?<Value>.+)$" } | % {
    Set-Item -Force -Path "env:\$($matches['Name'])" -Value $matches["Value"];
}
popd;
Write-Host "Visual Studio 2010 Command Prompt variables set." -ForegroundColor Yellow;
Here is my script to unlock a file in TFS even if the workspace doesn't belong to the user or on the users computer. If the command line arguments are not entered, the script will prompt for the arguments. It is a nice pattern that I may make a standard of in my scripts.
param(
  [string] $SourceControlFilePath = $(Read-Host "Source Control File Path"),
  [string] $Workspace = $(Read-Host "Workspace"),
  [string] $Username = $(Read-Host "Username")
)

& .\set-vsvars.ps1;

tf lock /lock:none $SourceControlFilePath /workspace:$Workspace`;$Username