16 November 2011

Multiple working folders for git on Windows XP

Multiple working folders for git on Windows XP (lucky me)

It is assumed that you have a git working copy of your project already in place at C:\code\myproject\ and that you want another copy of your project sharing all history but with a different branch checked out at C:\code\othercopy\

This is useful for:
  • rapidly switching between branches where a switch would normally cause a time consuming recompile / rebuild
  • tracking the branch structure of Visual SourceSafe (which you aren't using in this century are you?) aka VSS aka Visual Source Shredder (or maybe TFS)

This can be achieved by use of NTFS symbolic links (other related keywords: junction points, reparse points, hard links). Grab Sysinternals' Junction to provide access to NTFS symbolic links. Extract the contents and put junction.exe on your path.

Word of warning for NTFS symbolic links in Windows XP:
  • Windows Explorer in XP doesn't know about the NTFS symbolic links, and if you delete the containing folder it will delete the real copy of your linked folder (facepalm). Take backups first! You have been warned!
Open a command prompt and run the following commands:
cd c:\code\
mkdir othercopy
cd othercopy
mkdir .git
cd .git

junction hooks C:\code\myproject\.git\hooks
junction info C:\code\myproject\.git\info
junction logs C:\code\myproject\.git\logs
junction objects C:\code\myproject\.git\objects
junction refs C:\code\myproject\.git\refs

C:\code\othercopy\.git>copy C:\code\myproject\.git\* .
If you make a mistake, use "junction -d" to remove the branch point you have created, do not use explorer to delete a branch point as it will delete all your actual files.

You can now go into C:\code\othercopy\ and switch to a different branch, eg in git-bash:
cd c:
cd \code\othercopy\
git branch mynewbranch
git checkout -f mynewbranch
At this point you have two different checkouts sharing the same git data. Yay.

Word of warning for multiple working copies and git-extensions:
    • Git extensions gets confused if you remove a branch that one of your working copies is on; and incorrectly shows the folder as uninitialised. To resolve this use the context menu in explorer or the console to force checkout a different branch.
    This howto was written with "git version 1.7.6.msysgit.0" and git-extensions v2.26.

    References / see also:

    09 November 2011

    GpsPrune file matching



    I wanted to be able to load a whole bunch of gpx traces and see which one was where on the map. I've got a basic version working thought it's a bit rough around the edges.

    The code is available for you to grab from http://github.com/timabell/gpsprune

    And above is a pic of it in action, having opened a load of files at once and clicked on one of them in the list

    25 October 2011

    LVM + ReiserFS for the win

    LVM + ReiserFS for the win!

    It's so easy to add more space
    root@atom:~
    # lvextend -L +100G /dev/vg2/local
    Extending logical volume local to 200.00 GiB
    Logical volume local successfully resized
    root@atom:~
    # resize_reiserfs /dev/vg2/local
    resize_reiserfs 3.6.21 (2009 www.namesys.com)

    resize_reiserfs: On-line resizing finished successfully.
    root@atom:~
    # df -h /dev/mapper/vg2-local
    Filesystem Size Used Avail Use% Mounted on
    /dev/mapper/vg2-local
    200G 18G 183G 9% /media/local
    root@atom:~

    03 October 2011

    share your test data for your project

    Thought for the day:

    Completed projects should always inlcude SQL scripts for creating working test data (i.e. so you can see the code in action).

    I often see difficulties creating usable test data when picking up someone else's work.

    And I'd add to that, a readme file with how to use the test data.

    (short blog post, but too long for a tweet!)

    05 August 2011

    Simple water butt level meter

    Something new for me on this blog, a physical invention.

    I made a prototype with an idea of making them and selling them. It's designed to satisfy a personal niggle - it annoys me taking the lids off every time I want to see if they are full or empty. I then researched patents etc and decided it was a non-starter so here it is for you to make your own and for the good of the world.

    It's nothing ground breaking but I'm quite pleased with it.

    a diagram of my invention

    If you like here's the original svg of the diagram

    Please contact me if you like this / use this, just so I know it was worth the evening I spent drawing it :-)

    Contact me if you want me to make you one, I could sell them through ebay if there's a demand.

    To make it pretty I was thinking of putting a little fisherman on top with his rod holding the string for the level meter... (this would have the practical advantage of keeping the string straighter thus avoiding some of the error introduced by the bend in the simplest version).

    ----

    Alternatives:

    07 July 2011

    Auto-expanding django formset with jQuery

    As it took me quite a while to get it how I like it, here's the relevant bits for making a django formset (custom markup in a table), that automatically adds rows (formset forms) client-side / in the browser keeping up as you fill in the form.

    Do with the code as you wish, no licence needed.

    In the view (.html file server side) I have:

    @login_required
    def invoiceEdit(request, invoice_id):
    ...
    InlineInvoiceItemsFormSet = inlineformset_factory(Invoice, InvoiceItem, form=DeleteIfEmptyModelForm, formset=DeleteIfEmptyInlineFormSet, can_delete=True, extra=10)
    ...
    itemFormSet = InlineInvoiceItemsFormSet()
    ...
    return render_to_response('foo/edit.html', {'invoiceForm': invoiceForm, 'itemFormSet': itemFormSet, 'invoice': invoice}, context_instance=RequestContext(request))


    In the template I have:

    <script type="text/javascript">
    $(function() {
    setupInvoiceFormset();
    });

    var initialRows;

    function setupInvoiceFormset() {
    initialRows = parseInt($('#id_invoiceitem_set-INITIAL_FORMS').val());
    // remove all but last two empty rows
    resizeInvoiceFormset();
    // add handlers to all inputs to automate row adding
    $('.invoiceItemRow :input').blur(resizeInvoiceFormset);
    }

    const targetExtra = 2; // number of extra rows desired

    function resizeInvoiceFormset() {
    // count the blank rows at the end
    var rows = $('.invoiceItemRow').filter(':not(#templateItemRow)');
    var totalRows = rows.length
    var blankRows = countBlankRows(rows);
    var targetRowCount = totalRows - blankRows + targetExtra;
    targetRowCount = Math.max(targetRowCount,initialRows); // don't trim off real rows otherwise delete breaks
    if (totalRows > targetRowCount) {
    // if there too many blank rows remove the extra rows
    rows.slice(targetRowCount).remove(); // negative to strip from ends
    } else if (totalRows < targetRowCount) {
    // add new blank rows to bring the total up to the desired number
    for (var newRowIndex = totalRows; newRowIndex < targetRowCount; newRowIndex++) {
    addRow(newRowIndex);
    }
    } else {
    return;
    }
    // update the hidden form with the new form count
    $('#id_invoiceitem_set-TOTAL_FORMS').val(targetRowCount);
    }

    function countBlankRows(rows) {
    // count the empty rows from the bottom up, stopping at the first non-blank row
    var blankRows = 0;
    for (var i = rows.length -1; i>=0; i--) {
    if (isEmptyRow(rows[i])) {
    blankRows++;
    } else {
    break;
    }
    }
    return blankRows;
    }

    function isEmptyRow(row) {
    // loop through all the inputs in the row, return true if they are all blank
    // whitespace is ignored
    var inputs = $(row).find(':input').filter(':not(:hidden)');
    for (var j = 0; j < inputs.length; j++) {
    if ($.trim(inputs[j].value).length) {
    return false;
    }
    }
    return true;
    }

    function addRow(newRowIndex) {
    var newRow = $('#templateItemRow').clone(true);
    newRow.addClass('invoiceItemRow');
    newRow.removeAttr('id'); //prevent duplicated template row id
    newRow.show();
    // replace placeholder with row index
    newRow.find(':input').each(function() {
    $(this).attr("name", $(this).attr("name").replace('__prefix__', newRowIndex));
    $(this).attr("id", $(this).attr("id").replace('__prefix__', newRowIndex));
    });
    $('.invoiceItemRow:last').after(newRow);
    }
    </script>
    ...
    {{ itemFormSet.management_form }}
    <tr id="templateItemRow" class="invoiceItemRow" style="display: none;">
    <td><strong>Item:</strong></td>
    <td>
    {{ itemFormSet.empty_form.id }}
    {{ itemFormSet.empty_form.description }}
    {{ itemFormSet.empty_form.description.errors }}</td>
    <td class="price">£{{ itemFormSet.empty_form.price }} {{ itemFormSet.empty_form.price.errors }}</td></tr>
    {% for item in itemFormSet.forms %}
    <tr class="invoiceItemRow">
    <td><strong>Item:</strong></td>
    <td>
    {{ item.id }}
    {{ item.description }}
    {{ item.description.errors }}</td>
    <td class="price">£{{ item.price }} {{ item.price.errors }}</td></tr>
    {% endfor %}
    ...


    The result is a form that intuitively shrinks/grows as the content is added/removed.

    The javascript is of course actually in a separate .js file.

    References:


    Footnote. You may have noticed the delete-if-empty customisation which I like for usability. References for this at

    23 May 2011

    Data driven test in NUnit with csv source data

    I wanted to test a date parser across a large range of values so wanted a simple test harness to test all the values.

    The test framework options around c# / .net seem to be:
    • MSTest - can do csv via jet, but can't do inline test data which is something I also want.
    • NUnit - can do inline data driven test data (with the TestCase(data...) attribute), and has support for extending this via the TestCaseSource attribute.
    • xUnit - confusing (aka flexible), doesn't seem to get me to my end result any faster after a bit of searching around.
    I've used NUnit and combined TestCaseSource with a simple wrapper class around the csv parsing library

    To get this to work:
    • Save your csv file in your test project
    • add the file to your project (in visual studio 2008 in this case)
    • right-click on the csv file in solution explorer, click properties, change "Copy to Output Directory" to "Copy Always"
    • download the binaries (dlls) for csv reader from code project, add a reference to this in your test project
    • add a private method to your test class for reading the csv file and returning an enumarable (see code below)
    • add the TestCaseSource attribute to your test method(s) that you want to use the csv data, referencing your new IEnumerable method (see code below)


    using System.Collections.Generic;
    using System.IO;
    using LumenWorks.Framework.IO.Csv;
    using NUnit.Framework;
    
    namespace mytests
    {
        class MegaTests
        {
            [Test, TestCaseSource("GetTestData")]
            public void MyExample_Test(int data1, int data2, int expectedOutput)
            {
                var methodOutput = MethodUnderTest(data2, data1);
                Assert.AreEqual(expectedOutput, methodOutput, string.Format("Method failed for data1: {0}, data2: {1}", data1, data2));
            }
    
            private int MethodUnderTest(int data2, int data1)
            {
                return 42; //todo: real implementation
            }
    
            private IEnumerable<int[]> GetTestData()
            {
                using (var csv = new CsvReader(new StreamReader("test-data.csv"), true))
                {
                    while (csv.ReadNextRecord())
                    {
                        int data1 = int.Parse(csv[0]);
                        int data2 = int.Parse(csv[1]);
                        int expectedOutput = int.Parse(csv[2]);
                        yield return new[] { data1, data2, expectedOutput };
                    }
                }
            }
        }
    }


    references:

    18 May 2011

    Reliable javascript checkbox events


    Some sites have checkboxes which show/hide another element when you click them. This a handy feature, but not all sites take into account the fact that firefox remembers the contents of a form when you reload the page (this is a good thing).

    So here's how you avoid that with jQuery:

    <script type="text/javascript">
    $(function() {
    // initialise show/hide to match the checkbox value
    $('.targetelements').toggle($('#mycheckbox').attr('checked'));
    // attach click handler for show/hide to checkbox
    $('#mycheckbox').click(function(){ $('.targetelements').toggle(this.checked);})
    });
    </script>

    Simples!

    You could use the same principle without jQuery if you need to. Simply read the value of the checkbox with javascript the old fashioned way before deciding whether to hide when you initialise you page.