Posts by George Guimarães

When people talk about mirroring a git repository, usually we have a simple answer in mind:

Just git clone the repo and you’re set!!

However, what we want with mirroring is to replicate the state of an origin repository (or upstream repository). By state, we mean all the branches (including master) and all the tags as well.

You’ll need to do this when migrating your upstream repository to a new “home”, like when switching services like GitHub.

As with most tools, there’s a lot of ways to accomplish that, but I’ll be focusing on two of them. The difference lays on whether you already have a working copy of that repository or not.

Mirroring a git repository without a local copy

If you haven’t cloned the repository before, you can mirror it to a new home by

$ git clone --mirror git@example.com/upstream-repository.git
$ cd upstream-repository.git
$ git push --mirror git@example.com/new-location.git

This will get all the branches and tags that are available in the upstream repository and will replicate those into the new location.

Warning

Don’t use git push --mirror in repositories that weren’t cloned by --mirror as well. It’ll overwrite the remote repository with your local references (and your local branches). This is not what we want. Read the next section to discover what to do in these cases.

Also git clone --mirror is prefered over git clone --bare because the former also clones git notes and some other attributes.

Mirroring a git repository if you already have a local working copy

By working copy, we mean a “normal” repository, in which you have the files that are being tracked into git and where you perform commands like git add and so on.

In this case, you may have a lot of local branches and tags that you don’t want to copy to the new location. But you do have references to remote branches. You can view them with git branches -r. If you pay attention to that list, tough, you may notice that you have a lot of branches that were already deleted in the upstream repository. Why?

Cleaning old references to remote branches

By default, when you do a git fetch or git pull, git will not delete the references to branches that were deleted in the upstream repository (you may view them in your .git/refs/remotes dir). We need to clean those old references before mirroring them to a new location.

To do so, run

$ git fetch --prune

This will update your references to the origin repository and also clean the stale branches reported by git branch -r.

Finally, mirroring the repository to a new location

Now we’re ready to send those updated references back to the origin repository:

$ git push --prune git@example.com:/new-location.git +refs/remotes/origin/*:refs/heads/* +refs/tags/*:refs/tags/*

Ok, what just happened here?!

We want those references inside the .git/refs/remotes/origin to be the LOCAL references in the new location. The local references there will be stored in the refs/heads dir. Same thing happens to tags.

The + sign indicates that we want to overwrite any reference there may already exist.

--prune means we want to delete any reference that may exist there if we don’t have such reference in our refs/remotes/origin/* (and tags) references.

Conclusion

Git is certainly not an easy tool to learn. Although when you do, it turns into a very powerful and flexible tool.

If you want to learn more about it, please see the excelent book written by Scott Chacon and available for free.

What about you? Have any tips on git you want to share?


Para quem acompanha a comunidade Ruby brasileira, não é nenhuma novidade que nos dias 3 e 4 de Novembro teremos mais uma edição do RubyConf BR.

Assim como no ano passado, o evento acontece no Centro de Convenções Frei Caneca, perto da Av. Paulista e de fácil acesso.

Toda a equipe da PlataformaTec estará lá. Além disso, José Valim irá nos contar sobre a experiência de criar uma nova linguagem (Elixir) e como isso o fez aprender mais sobre Ruby. Será no dia 4, às 17h10.

Não deixe de nos procurar para bater um papo bacana. Aproveite e fique também para o Happy Hour que sempre acontece após o evento. É a oportunidade para conversar e conhecer todo mundo que trabalha com Ruby no Brasil. Vai perder essa? Inscreva-se!

Since moving to Mac, I always find it surprising that the default Mac OS X Ruby and IRB doesn’t allow inputs with accented characters. So, you cannot do

name = "George Guimarães"

This is annoying for brazilians and anyone who uses non-ASCII characters. The problem is that the default Ruby in Mac OS X isn’t linked against readline. A simple solution is to compile readline on your system, and then compile your own ruby binaries. A better one is to use tools that automate this process.

This post by Christopher Sexton uses homebrew to compile readline-0.6 but compiles ruby by hand. Since we love RVM, we’ll use it too.

If you use Homebrew (and you *should*), just do

brew install readline
brew link readline

Beware: linking readline into your system may break other tools that depends on readline source to compile. It was harmless on my system.

Ok, so now we want to build a new ruby and irb binaries. I wanted to use ruby 1.8.7-p248, so:

rvm install 1.8.7-p248 -C --enable-shared,--with-readline-dir=/usr/local

This tells the configure script to enable shared library linking (it is the default), and to search for readline in /usr/local (homebrew has just linked readline there). You may have to use --force if you already have this ruby version compiled. Newer versions of RVM do not need --force.

Now you can use accented and unicode characters on keyboard input in IRB using ruby 1.8.7.

And you? Do you have any tricks with IRB that you may want to share? Do you use Wirble, Utility Belt or others?

O Dev in Sampa acontecerá no dia 28 de novembro (sábado) e, obviamente, será em São Paulo.

O José Valim participará falando sobre “Tópicos de machine learning: classificação de textos”. Será uma palestra baseada no seu trabalho de mestrado na Politécnica de Turim, na Itália.

Além disso, teremos palestras sobre jogos em Python, Arduino, testes em Javascript, Treetop, XMPP… o evento promete!

Confira a programação do evento!

Sometimes users want to slice and dice data as they wish. In such scenarios, it’s usual to export the data in a tabular format so your users can use any spreadsheet editor and do whatever they want.

Usually, we do that using CSV, right? OpenOffice and other editors can open CSV files flawlessly. Just double-click an CSV file and voilà.

However, Excel has some gotchas. If you double-click a CSV file, Excel will open your data with everything in just one column. Of course you can go to Tools > Import, browse to the file, set up comma as separator and then after some clicks you get your data as you wish. This is not user-friendly at all and this is not something we want to explain to our clients. Our clients must be able to simply double-click the my_data.csv file and see the data well structured.

So, here’s what we do at Plataforma to deal with Excel formats.

It’s TSV, not CSV, dude!

First of all, Excel expects your data with tabulations as fields separator. So, what you need is actually a TSV (tab-separated values).

If you are using FasterCSV, you just need to do:

tsv_str = FasterCSV.generate(:col_sep => "\t") do |tsv|
  tsv << headers
  # append your beautiful data here
end

Keep this in mind. Excel demands tabulations, not commas! But there are worse things to come…

No newline in fields

Excel doesnt’ like when you put “\n” inside fields. Although fields are separated by tabulations, it appears Excel can’t cope with extra newlines.

So, if you have some text fields in your model, beware that it may contain newlines and you need to strip those before exporting your data to Excel.

Forget about UTF-8. Use UTF-16!

One of the most hidden specifications of Excel is that it expects our TSV files to be encoded using UTF-16 Little Endian. Did you know that? Well, we didn’t!

Some sources even say that this is the only Unicode format supported in Excel.

What’s the difference between UTF-8 and UTF-16? UTF-8 is a variable byte encoding in which characters may use up to 4 bytes, however for western languages it usually uses one or two bytes. UTF-16 characters always uses at least 2 bytes (remember that characters and bytes are two different things in Unicode “slang”). Generally speaking, UTF-16 takes more space in the filesystem (most of the time).

There’s also the Little Endian part. UTF-16 always uses a pair of bytes to represent a character, however we need to know what’s the correct order of those bytes. We won’t get into details here, but the order is indicated by Byte-order Mark (BOM). In practice, the BOM in UTF-16 will add two bytes in the beginning of a file (you can see that in a hexeditor).

One way to convert your TSV string (generated by FasterCSV) is with Iconv, a tool written for the GNU C Library. Luckly, Iconv is well wrapped in pure Ruby joy. It’s inside Ruby’s standard library and you just need to require it.

However, if you convert an string to UTF-16 Little Endian, Iconv will NOT put the BOM in the beginning. This is compatible with the Unicode FAQ. But since Excel is way out of the standards, you must manually insert the BOM.

You can use Iconv just before sending the file to the user in your controller. It will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'iconv'
 
class ProjectsController < ActionController::Base
  BOM = "\377\376" #Byte Order Mark
 
  def index
    @projects = Project.all
    respond_to do |format|
      format.html
      format.csv { export_csv(@projects) }
    end
  end
 
protected
 
  def export_csv(projects)
    filename = I18n.l(Time.now, :format => :short) + "- Projects.csv"
    content = Project.to_csv(projects)
    content = BOM + Iconv.conv("utf-16le", "utf-8", content)
    send_data content, :filename => filename
  end
end

You can see that we are still using CSV as an extension to the file. This is because TSV files are not usually associated with Excel. This excerpt uses a timestamp as the filename, which is generally a good practice.

Wrapping up

So, these are the three rules for dealing with Excel-friendly-CSV:

  1. Use tabulations, not commas.
  2. Fields must NOT contain newlines.
  3. Use UTF-16 Little Endian to send the file to the user. And include a Little Endian BOM manually.

Keep this in mind and you’ll never have to explain to your clients how to open the data export in Excel.

There is one last issue: OpenOffice will NOT open files in the Excel “specification” easily. Google Analytics solves this by showing two links to the user: “Export to CSV” and “Export to Excel”. The first is a regular CSV file and the second is the specially crafted Excel-friendly TSV file.

And blame Microsoft for this odd behaviour.

UPDATE: @danielvlopes pointed us an already existing solution which encapsulates the process described in this post, called csv_builder. You just need to set the @output_encoding to use the “utf-16″ (beware of the BOM).

@jncoward also provided a link to the spreadsheet gem, which handles directly Excel formats. TSV is a much simpler and faster format, but the spreadsheet gem might be useful to you in more complex cases.

Finally, Kieran pointed in the comments that we can have XML spreadsheets, while Niko and Chris told us that we can you can also work with HTML tables in Excel, which is simple as well and even allows formatting.

To see this post in English, click here.

Às vezes os usuários querem manipular os dados de diversos modos. Nesses cenários, é comum exportar os dados num formato de tabela para que se possa usar um editor de planilhas e então filtrar, particionar e mudar os dados da forma que o usuário quiser.

Generalmente, fazemos isso usando CSV, certo? O OpenOffice e outros editores podem abrir arquivos CSV sem problemas. É só clicar duas vezes no arquivo CSV e voilà, os dados aparecem.

Entretanto, o Excel não funciona exatamente da mesma forma. Se você clicar duas vezes no arquivo, o Excel vai lhe mostrar todo o conteúdo em apenas uma coluna. Claro que você poderia ir em Ferramentas > Importar dados, navegar até o arquivo, selecionar a vírgula como separador de campos e então, após alguns cliques você terá seus dados como desejado. Isso não é nada amigável para o usuário e é o tipo de coisa que não queremos explicar para nossos clientes. Nossos clientes devem ser capazes de simplemente clicar duas vezes no arquivo meus_dados.csv e ver os dados bem estruturados.

Tendo isso em mente, é assim que fazemos na Plataforma para lidar com os formatos do Excel.

É TSV, e não CSV, rapaz!

O Excel espera que seus dados venham com tabulações como separador de campo. Então, o que você precisa na verdade é um TSV (tab-separated values).

Se você está usando o FasterCSV, só precisa fazer:

tsv_str = FasterCSV.generate(:col_sep => "\t") do |tsv|
  tsv << headers
  # coloque seus bonitos dados aqui...
end

Não esqueça! Excel exige tabulações, e não vírgulas. Mas tem coisa pior vindo…

Sem quebras de linha nos campos

O Excel não gosta quando você põe um “\n” dentro dos campos. Apesar dos campos serem separados por tabulações, ele parece não entender o que a quebra de linha está fazendo ali.

Então, se você tem campos de texto no seu modelo, tome cuidado. Eles podem conter quebras de linhas e você precisa tirá-las antes de exportar seus dados para o Excel.

Esqueça UTF-8. Use UTF-16!

Uma das especificações mais escondidas do Excel é que ele espera que seus arquivos TSV sejam codificados usando UTF-16 Little Endian. Você sabia disso? Bem, nós não!

Alguns até dizem que este é o único formato Unicode suportado pelo Excel.

E qual a diferença entre UTF-8 e UTF-16? O UTF-8 é uma codificação de tamanho variável, cujos caracteres podem usar até 4 bytes, mas para idiomas ocidentais generalmente é usado um ou dois bytes. Caracteres UTF-16 usam sempre 2 bytes para serem representados (lembre-se que caracteres e bytes são duas coisas bem diferentes no mundo Unicode). Simplificando bastante as coisas, UTF-16 usa mais espaço (na maioria das vezes).

E tem também a parte do Little Endian. O UTF-16 sempre usa um par de bytes para representar um caractere. Contudo, nós precisamos saber qual a ordem certa desses bytes. Não vamos entrar em detalhes aqui, mas essa ordem é indicada pelo Byte-order Mark (BOM). Na prática, o BOM no UTF-16 vai adicionar dois bytes no começo do seu arquivo (você pode ver isso usando um editor hexadecimal).

Um dos jeitos de converter sua string TSV (gerado pelo FasterCSV) é com o Iconv, uma ferramenta escrita para a Biblioteca C do GNU. Felizmente, o Iconv está bem empacotado em pura felicidade Rubyana. Está dentro do biblioteca padrão do Ruby e vocÊ só precisar dar um require nela.

Entretando, se você converter uma string para UTF-16 Little Endiand, Iconv NÃO irá colocar o BOM no começo. Isso é compatível com a especificação do Unicode. Mas já que o Excel está totalmente fora dos padrões, você deve inserir manualmente o BOM para aumentar a compatibilidade.

Você pode usar o Iconv na hora que enviar o arquivo ao usuário dentro do seu controller. Ficaria mais ou menos assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'iconv'
 
class ProjectsController < ActionController::Base
  BOM = "\377\376" #Byte Order Mark
 
  def index
    @projects = Project.all
    respond_to do |format|
      format.html
      format.csv { export_csv(@projects) }
    end
  end
 
protected
 
  def export_csv(projects)
    filename = I18n.l(Time.now, :format => :short) + "- Projects.csv"
    content = Project.to_csv(projects)
    content = BOM + Iconv.conv("utf-16le", "utf-8", content)
    send_data content, :filename => filename
  end
end

Perceba que estamos usando CSV como extensão do arquivo. Isso é porque geralmente arquivos TSV não estão associados ao Excel. Este trecho usa um timestamp como nome de arquivo, o que é geralmente uma boa prática.

Resumindo

Essas são as 3 leis para lidar com CSVs para o Excel:

  1. Use tabulações, e não vírgulas.
  2. Campos NÃO podem conter quebras de linha.
  3. Use UTF-16 Little Endian para enviar o arquivo ao usuário. E adicione o BOM do Little Endian manualmente.

Tenha isso em mente e você nunca terá que explicar para seus clientes como abrir os dados exportados no Excel.

Há um último problema. O OpenOffice não irá abrir facilmente arquivos feitos na “especificação” do Excel. O Google Analytics resolve este problema mostrando dois links ao usuário: “Exportar para CSV” e “Exportar para Excel”. O primeiro é o CSV normal e o segundo é o arquivo TSV feito especialmente para o Excel.

E culpe a Microsoft por este comportamento estranho.

UPDATE: @danielvlopes nos avisou de uma solução existente que encapsula o processo deste post, chamado csv_builder. Você só precisa configurar o @output_encoding para usar “utf-16″ (preste atenção com o BOM).

@jncoward também enviou um link para a gem spreadsheet, que escreve formatos nativos do Excel. TSV é um formato mais simples e mais rápido, mas a gem spreadsheet pode lhe ajudar em casos mais complexos.