Wednesday, February 11, 2015

How to Git rm a file with special characters in the name?

Git GUI tools like SourceTree and the GitHub client are great.  But there are plenty of times when the command line is exactly what you need to get the job done.

In particular if you go looking on the 'net for tips on fixing particular problems or carrying out operations that you don't do very often, you'll usually find the answer as a sequence of command lines to type out.

Also sometimes you are on a remote server via ssh and using git to fetch some project or deal with your web project managed via git.  In these cases you won't have you graphical client.

Well, here's a good git trick:  git add -i

~/depot/SpaceBotAlphaUpdate $ git add -i
           staged     unstaged path
  1:    unchanged        +0/-0 SpaceBotAlpha.spritebuilder/Icon

*** Commands ***
  1: status   2: update   3: revert   4: add untracked
  5: patch   6: diff   7: quit   8: help
What now> 2
           staged     unstaged path
  1:    unchanged        +0/-0 SpaceBotAlpha.spritebuilder/Icon
Update>> 1
           staged     unstaged path
* 1:    unchanged        +0/-0 SpaceBotAlpha.spritebuilder/Icon
Update>> ^D
updated one path

*** Commands ***
  1: status   2: update   3: revert   4: add untracked
  5: patch   6: diff   7: quit   8: help
What now> q
Bye.

~/depot/SpaceBotAlphaUpdate $ 


This command invokes the interactive form of git add, which allows you to select the files you want to add for update or as new files (which were previously untracked).

In this case SpriteBuilder had previously created a bogus file of zero length.  The last two characters in the name were unprintable and were being displayed in the console as a question mark.  The file looked like "Icon?" or "Icon/r" depending on what tool was used.

SourceTree was not able to process the file - I could select it but could not git rm it.  Finder was the same.  I could select but not delete it.

The following command allowed me to remove it from the shell:

rm -- Icon*


Here the -- marks the end of switches to the shell so that everything after -- is treated as a file name. When the shell expands the Icon* into the non-printing characters, the -- ensures that those characters aren't interpreted as switches or other controls to rm. So it was gone from the file system. Now how to remove it from source control?

The above git add -i session was the answer.

After invoking git add -i you get a basic status listing.  Here in the first line its showing that a 0 length file is currently changed in the working set (the unstaged column) but is unchanged in the cache of files about to be committed.

I type a '2' to enter the update sub-menu, where again I see the same line of output.  Typing a 1 here chooses the file listed with index 1 to be updated in the cache index of files for commit.  If the file had been modified by changing the code contents this would have added that file to the commit.

The real power comes from adding large numbers of files by typing for example

1-33,45-62

...where you select two ranges of files, and exclude the others.  Very handy for dividing large change sets up into one or more commits by selecting only the changed files you want.

Note the typing of control-D (end-of-file) to tell add -i to pop back from the sub-menu.  Here if you want you can go into the add untracked menu by typing either 'a' or '4'; to put new previously untracked files under source control.

Once you're all done, control-D out to the top menu, and do a final status with 's' to check you have what you want.  Now pop out of git add -i altogether with a 'q' for quit and type

git commit -a "My amazing changes"

to finalize the commit.

Open Source Hacking

Cocos2D is my framework of choice for games these days: its native and supports both iOS devices like the new iPhone 6, and also Android via the Apportable system which builds native NDK binaries out of Objective-C source code.
Space Bot Alpha's word balloons

Another thing to like about it is that its Open Source, which means when there's a pain point you can dive in and fix it yourself!  This is what I've been doing for the last few days, as an annoying regression appeared in Space Bot Alpha after recent updates to Cocos2D.  My speech bubbles in the current AppStore version feature coloured text, but this had stopped working in the version I'm trying to get out for release!  Also there were also problems with line splitting when the label had a width specified to fit the text into.

CCLabelBMFont is a pretty nice class: it allows making high performance labels that re-use their pre-rendered font glyphs thus avoiding overhead during those 60 frames-per-second when you might be changing the text in a label.  Changing the text simply reassigns an existing CCSprite that currently points to a texture rect to point to a different rect inside the batch-node (which is what the label is under the hood).  Performance plus!



However the re-use and hackery over the last months getting new features in to Cocos2D had meant that the old system of using getChildByTag: had gone away and an attempt to work around this had defeated the logic of being able to address individual sprites.  Also the ability to set width and subsequently wrap had been implemented by internally modifying the actual string to include more line feeds.  Basically the promise of being able to address the sprites that matched characters in the string had stopped working.

I addressed the problem by making a private sub-class of CCSprite that handles hard and soft line breaks by an enum property.  The sprites are also linked together in a list so that iterating over them doesn't require using getChildByTag:  I guess the reason it was done this way was that in Cocos2D its possible to add child nodes to anything - including the CCLabelBMFont - and you might do this if you had some graphic that you wanted to move along with the label.  The tagged children were known to be the font glyphs and not some other child that might have been added.

As well as fixing all these issues my patch adds in a function that allows addressing these child nodes via a range into the original string (which works because no extra line feeds are required) and looks like this:

    NSString *highlightString = @"even if the sound";
    NSString *testString = @"Supercalifragilisticexpialidocious even if the sound of it is something quite atrocious";
    NSRange highlightRange = [testString rangeOfString:highlightString];
    CCLabelBMFont *label = [CCLabelBMFont labelWithString:testString fntFile:@"arial16.fnt" width:currentWidth alignment:CCTextAlignmentLeft];
    [label setColor:[CCColor whiteColor]];
    NSArray *highlightChars = [label characterSpritesForRange:highlightRange];
    for (CCSprite *fontSprite in highlightChars)
    {
        [fontSprite setColor:[CCColor cyanColor]];
    }
    [label setPositionType:CCPositionTypeNormalized];
    [label setPosition:ccp(0.5f, 0.5f)];
    [[self contentNode] addChild:label];

    NSString *explanation = @"Styling font characters - color";


You can even get bold and italic or other effects using a similar technique.

    NSString *boldString = @"boldly go";
    NSString *italicString = @"new civilizations";
    NSString *testString = @"to seek out new life and new civilizations, to boldly go where no man has gone before.";
    NSRange boldRange = [testString rangeOfString:boldString];
    NSRange italicRange = [testString rangeOfString:italicString];
    CCLabelBMFont *label = [CCLabelBMFont labelWithString:testString fntFile:@"arial16.fnt" width:currentWidth alignment:CCTextAlignmentLeft];
    [label setColor:[CCColor whiteColor]];
    {
        NSArray *highlightChars = [label characterSpritesForRange:boldRange];
        CCEffectBloom *boldEffect = [CCEffectBloom effectWithBlurRadius:1 intensity:0.5 luminanceThreshold:0.1];
        for (CCSprite *fontSprite in highlightChars)
        {
            [fontSprite setEffect:boldEffect];
        }
    }
    {
        NSArray *highlightChars = [label characterSpritesForRange:italicRange];
        for (CCSprite *fontSprite in highlightChars)
        {
            [fontSprite setSkewX:20.0f];
        }

    }



My pull request on the Cocos2D Github project has been submitted so lets see what upstream Cocos2D folks make of it!