Converting SVN repos to Git (for dummies)

August 29th, 2014

We used to use Subversion (SVN) for all of our source control at work a few years ago. Then, we switched over to Git a while back. I had several old projects still stored in our SVN account at codebasehq.com and I needed to migrate them to Git repos. A few of them were work projects that needed to be moved to Github. Others were personal projects that I wanted to store on BitBucket. I spent several hours combing through blog posts figuring this out, so I thought I’d make a “dummies” guide on how to do this for people like me who are not Git experts. For this conversion, you will need:

  • Sourcetree – a great, free Git GUI. Get it here
  • Git installed on the command line. Sourcetree has it’s own Git client, but we will need to do some command line stuff. Unfortunately, I can’t offer any tips on setting up Git. I did it several years ago and didn’t make any notes on it.
  • A mac. Sorry, but these instructions are for mac users. They will be very similar for PC users, but may need slight modification.

Once you have those things, here’s what you do:

  1. Create a folder on your machine where you will store your files during this process. I created one called “SVN_TO_GIT” on my desktop.
  2. Log in to your SVN account and find the SVN repo you want to convert. browse through the files and try to get a list of the user ids that have made commits. For example, in one of my work projects, I found the following user IDs:
    andytb9
    johnboy
    andy
    john
    toolbox
  3. Create an authors.txt file to convert the old svn IDs to git emails. This is really simple. Just save a blank text file in the folder you created above as “authors.txt”. In this file, add a line for each of the user IDs you found in Step 2. These lines specify how to convert the SVN user IDs:
    andy = Andy Watt <andy@gmail.com>
    andytb9 = Andy Watt <andy@gmail.com>
    johnboy = John Doe <john@gmail.com>
    john = John Doe <john@gmail.com>
    toolbox = Andy Watt <andy@gmail.com>

    **Note how you can actually re-map the project contributors. I could simply assign all SVN IDs to my email if I want.

  4. Create a subfolder in the SVN_TO_GIT folder you already created. This folder will hold the repo that you will convert, so name it something that makes sense. For this example, I will name mine “REPO_1”.
  5. Open Sourcetree and clone the SVN repo into the REPO_1 folder you created above. Clone it as if it was a Git repo, but enter the URL of the SVN repo. You will probably need to enter your user name and password for the SVN account here.svn-to-git-1-clone-svn
    Sourcetree will notice that it’s an SVN repo and ask you for some details on how to convert it:

    • Create local repository of type: Git
    • Convert from SVN revision: 1
    • Author map file: (browse to your authors.txt file)

    svn-to-git-2-options-svn
    BTW, after you enter your SVN credentials, make sure that Sourcetree is still planning to save your cloned repo into the REPO_1 folder. Sourcetree has a quirk that sometimes changes this destination folder unexpectedly.

  6. Click “clone”
  7. If you get an error about “Can’t locate SVN/Core.pm”, go here and follow the instructions to get everything hooked up correctly.
    svn-to-git-3-cant-locate-core-svn
  8. Or… if you get a crazy error like this:
    Can't locate object method path via package Git::SVN at /usr/local/git/lib/perl5/site_perl/Git/SVN/Ra.pm line 338.
    • go to Sourcetree > Preferences.
    • click on the Git tab at the top.
    • near the bottom, change the Git version to “Use system Git”
    • locate the system Git executable file on your machine. You can do a search in finder for “git” and look for a file with that name – the icon will look like a terminal window.
    • that should fix the error

    svn-to-git-error-use-system-git

  9. Show the output and check for errors. If it clones successfully, you’re almost done! If not, you probably forgot a user in the authors.txt file. Scan the errors to see if it is bonking on a user id. Add the conversion to your authors.txt file and try again.
    svn-to-git-error-author-not-defined
  10. Log in to Github, Bitbucket, or the Git account of your choice. Create a new repo. Be sure to specify whether this is a public or private repo. I did not add a readme, but it’s ok if you do.
  11. Copy the url for the new Github repo that you created (in my example: https://github.com/andy/dummy-project.git)
  12. On your mac, open a terminal window
  13. cd (change directory) to the SVN_TO_GIT/REPO_1 folder that now contains your cloned repo.
  14. run the following commands:
    git remote set-url origin
    git remote set-url origin https://github.com/andy/dummy-project.git
    git push origin master
  15. Alternative method if the previous steps didn’t work for you:
    cd /path/to/my/repogit 
    remote add origin https://tb9andy@bitbucket.org/andy/dummy-project.git
    git push -u origin --all 
    git push -u origin --tags
  16. After it finishes, log in to github and take a look at your awesome new git repo with all of its commit history. Pat yourself on the back for your awesomeness.
  17. Create a new folder on your local machine and clone the new github repo into it. This is the local folder you should work from. Don’t work from the SVN_TO_GIT/REPO_1 folder you created. You should be able to delete that now.
  18. You should remove the old SVN remote and any local references to it on your machine once you’re sure that everything has converted correctly.

If you need to convert an old SVN repository to Git, I hope this will save you some time. If I missed anything, leave a comment and let me know.


A Brief Lesson in Email Spam

August 21st, 2014

I received an urgent support ticket yesterday from a client’s hosting provider. It said that email spam was originating from our client’s website. It was the typical email that most people refer to as a “Nigerian email scam” or a “419 scam.” Here’s the email:

Dear Friend,

Greetings in the name of God,Please let this not sound strange to you
for my only surviving lawyer who would have done this died early this
year.

I prayed and got your email id from your country guestbook.I am Mrs Rose
Holtsbery from London,I am 58 years old,i am suffering from a long time
cancer of the lungs which also affected my brain, From all indication my
conditions is really deteriorating and it is quite obvious that,
according to my doctors they have advised me that i may not live for the
next two months,this is because the cancer stage has gotten to a very
bad stage.

I was brought up from a motherless babies home was married to my late
husband for twenty years without a child,my husband died in a fatal
motor accident Before his death we were true believers.Since his death I
decided not to re-marry,I sold all my inherited belongings and deposited
all the sum of 10million dollars with a Bank.

Presently, this money is still with the bank and the management just
wrote me to come forward and claim my money because they have kept it
for so long or rather issue a letter of authorization to somebody to
receive it on my behalf since I can not come over because of my illness,
or they get it confiscated.

Presently, I'm with my laptop in a hospital here in Switzerland where I
have been undergoing treatment for cancer of the lungs. My doctors have
told me that I have only a few months to live.It is my last wish to see
that this money is invested to any organization of your choice and
distributed each year among the charity organization,the poor and the
motherless babies home.

I want you as God fearing person, to also use this money to fund
church,mosque, orphanages and widows,I took this decision before i rest
in peace because my time will soon be up.

As soon as I receive your reply I shall give you the contact of my
Doctor Legal practioner(lawyer) who will issue you a letter of Authorit
y that will prove you as the new beneficiary of my fund.

Provide me with your information so i can send it to the bank as the new
beneficiary and issue you a letter of authorization.

Below is the information needed from you:<

FULL NAMES:__________SEX: _____ AGE: ______MARITAL
STATUS:_______________COUNTRY: ______
CONTACT ADDRESS: ________________________PHONE NO#___________FAX
NO#_________________OCCUPATION:______________

Please assure me that you will act accordingly as I stated herein.Hoping
to hear from you soon.

Mrs Rose Holtsbery

I had no idea where to begin solving this issue, so I called Rackspace, the hosting company. The support folks confirmed that the email was originating from the server and ran some diagnostics to locate the source. We quickly found the problem and I got an education in spamming…

On the client’s website, there is a folder cleverly named “uploads,” which stores all images and documents uploaded through the content management system. Some lazy web developer (me) had set the permissions on the folder to 777. What this means is that basically anyone has read/write privileges on this folder. Scammers use bots (automated scripts) to crawl the web looking for open folders like this. When they find one, they attempt to write a simple PHP script into the folder. This script is the spam mailer, but it uses your server to do the dirty work. We found a script named “isunn.php” in the uploads folder. If you navigated to the file in your browser, it looked like this:

Click on the image to see a larger version:
spam-GUI

Pretty slick. It’s basically an email spam GUI. You simply enter the details, including the number of people you want to email and hit the SEND button. Easy peasy.

Anyway, I thought it was interesting to get a brief education in the mechanics of spamming. And the lesson is: don’t ever set folder permissions to 777 on your web server.


SOLVED: IntelliJ IDEA LESS compiler failure

August 15th, 2014

intellij Idea less compiler failI use IntelliJ IDEA for all of my web development work – it’s simply the best web IDE on the market, but I ran into a weird problem with the built-in LESS compiler recently. I cloned a project from the Github repo and set it up in IntelliJ. Everything was going well until I tried to compile the LESS files. I was getting weird errors whenever I tried to compile any LESS files in the project. Errors like this:

LESS CSS Compiler Error
mobile.less: Name Error: variable @screen-sm-max is undefined (line 2, column 20) near @media (max-width: @screen-sm-max) {

(This is weird because this variable is defined correctly)

or this:

LESS CSS Compiler Error
styles.less: org.mozilla.javascript.UniqueTag@15923b2d: NOT_FOUND Error: java.io.IOException: No such file file:/Users/admin/Desktop/_CLIENTS/Website%20build/BUILD/GIT/_WEB/styles/bootstrap/bootstrap.less (line -1, column -1)

(This is weird because it’s failing to compile Bootstrap, which I know is fine)

There was obviously something wrong with my setup because it worked fine for other developers. I was getting these errors when I used the “Compile to CSS” function or when I set up a LESS file watcher. I upgraded my LESS version. I spent a lot of time trying a lot of different things until I stumbled across this obscure thread.

That’s right. You can’t have any whitespace in the file path. My mistake was that the project was located in this folder:
Desktop/_CLIENTS/Website build/BUILD/GIT
Notice the space in “Website build.” I changed to folder name to “Website_build” and it worked. Note that manually adding “%20” into the file watcher path doesn’t work – you absolutely can NOT have any spaces in the path. I was kind of shocked at this problem. I thought we had gotten over this issue 20 years ago. Anyway, I’ve learned my lesson. No more folders with whitespace in the names. If this saves someone else a little time, then my wasted time will be worth something.


iOS: Limiting the character count on UITextField

August 5th, 2014

For some reason, I couldn’t find this anywhere else on the web, so here it is. To set a maximum character count on a UITextField, simply implement this UITextFieldDelegate method:

-(BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    int maxLength = 64;
    //you only need this check if you have more than one textfield on the view:
    if (textField == self.nameTextField) {
        if (textField.text.length - range.length + string.length > maxLength) {
            if (string.length > 1) { // only show popup if cut-and-pasting:
                NSString *message = [NSString stringWithFormat:@"That name is too long. Keep it under %d characters.", maxLength];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops!"
                                                                message:message
                                                               delegate:nil
                                                      cancelButtonTitle:@"OK"
                                                      otherButtonTitles:nil];
                [alert show];
            }
            return NO;
        }
    }
    return YES;
}

Remember, this is a delegate method, so you have to specify that your UIViewController implements it:

@interface SomeViewController () <UITextFieldDelegate>

…and you have to set the delegate for the textfield:

self.nameTextField.delegate = self;

This can also be done for a UITextView with its delegate method:

-(BOOL) textView:(UITextField *)textView shouldChangeTextInRange:(NSRange)range replacementString:(NSString *)text {
    int maxLength = 64;
    //you only need this check if you have more than one textview on the view:
    if (textView == self.descriptionTextView) {
        if (textView.text.length - range.length + text.length > maxLength) {
            if (text.length > 1) { // only show popup if cut-and-pasting:
                NSString *message = [NSString stringWithFormat:@"That description is too long. Keep it under %d characters.", maxLength];
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops!"
                                                                message:message
                                                               delegate:nil
                                                      cancelButtonTitle:@"OK"
                                                      otherButtonTitles:nil];
                [alert show];
            }
            return NO;
        }
    }
    return YES;
}

Top