Using git bisect to debug Etherpad issues

Sometimes stuff gets broken due to new commits, we need to know which commit broke functionality.

To do this

git pull
git checkout develop

Ensure the bug exists then

git checkout master

Ensure the bug doesn’t exist. If it does checkout older versions IE git checkout release/1.2.8
Next begin the bisect process..

git bisect start

Tell bisect that master is fine

git bisect good

Tell bisect that develop is bad

git checkout develop
git bisect bad

Bisect will then deliver you a commit state, test this new version..

bin/run.sh

Test, Control C.. Did it work? If so…

git bisect good

Did it not work? If so..

git bisect bad

Rinse repeat ‘git bisect good’ and/or ‘git bisect bad’ until it delivers you with a commit SHA then create an issue including the details from the SHA.

Basically bisect will tell you which commit introduced the bug.

Script for checking out pull request

I often have to test specific pull requests and tanks to this post on how to checkout git pull requests I was able to quickly bash together a script

I wrote this to ~/checkoutPR.sh

echo "Getting Pull request #$1"
git fetch origin pull/$1/head:pr-$1
git checkout pr-$1

then

chmod 775 ~/checkoutPR.sh

Then jump into the repo folder
Grab the Pull request # from the URL, replacing %pullrqeuestnumber% with the pull request #

~/checkoutPR.sh %pullrqeuestnumber%

Simple but useful.

The output is something like this

jose@debian:~/etherpad-lite$ ~/./checkoutPR.sh 1672
Getting Pull request #1672
remote: Counting objects: 142, done.
remote: Compressing objects: 100% (43/43), done.
remote: Total 124 (delta 101), reused 104 (delta 81)
Receiving objects: 100% (124/124), 13.58 KiB, done.
Resolving deltas: 100% (101/101), completed with 15 local objects.
From github.com:ether/etherpad-lite
 * [new branch]      refs/pull/1672/head -> pr-1672
Switched to branch 'pr-1672'

Doing Collaborative editing viewport scrolling right

Getting scrolling in an editor right is a complex task with various Gotchas. Getting scrolling right is in a collaborative editing environment is fraught with even more perils.   Most editors, both collaborative and non collaborative get scrolling wrong, yours probably does too.   But fear not! In this post I’m going to explain some edge case behaviours and how you should go about handling them.

I hold WordPress in really high regard yet WordPress as an example gets this very wrong(for example hitting page down in your editor will lose your caret if your document is greater than the height of your editor), TinyMCE is probably not to blame for this, WYSIWYG editors on the web in general are broken.

The behaviour for how editors scroll with documents has become a relatively normal experience. When you hit enter you expect to create a new line and for your browser to continue bringing that newline into your viewport.

When we talk about a viewport we mean something like this

  --------------
  |  document  |
|----------------|
|                |
|    viewport    |
|                |
|----------------|
  |            |
  --------------

As you can see the viewport shows a portion(or if the document is smaller than the viewport the whole) of the document and as a newline is created the viewport is moved down by the pixel height of the new line.  If you are a WYSIWYG or Etherpad user think of it as the large white panel where you can edit text.

So we know that moving the Viewport Offset Top to the right location on a document in a non collaborative environment is really easy, yet there are some behaviours we take for granted.

Taken for granted

Using left arrow key moves the caret one position left until it hits the beginning of a row then it goes to the last position on the row above

Using the up/down arrows move our caret up, maintaining X(IE 5) position, when the caret hits the Viewpoint offset minus the Above line height the viewport is moved by minus the Above line height. Similar happens for the down arrow. If there is no line X content the caret X position is 0. 0 is not retained though, if the user presses up/down again the caret maintains it’s original trajectory, IE 5

Using the page up/down arrows move our caret up to the first visible line in the viewport. Hitting page up again will take the current viewport height and move the first fully visible line in the new viewport position. Page down does similar.

Page up:

|----------------|
|    viewport    |
|                |
|----------------| 
  |  document  |
  --------------

Page down:

  --------------
  |  document  |
  |            |
|----------------|
|                |
|    viewport    |
|                |
|----------------|

If your editor gets these wrong then you have serious problems.

Gotchas

It’s at this point that collaborative editing throws some gotchas in.

Ever changing line heights above content

As you type this happens..
State a:

  --------------
  |  document  |
  |            |
|----------------|
|                |
|    viewport    |
|                |
|----------------|

State b:

  --------------
  |  document  |
  |            |
  |            |
|----------------|
|                |
|    viewport    |
|                |
|----------------|

As you can see here another user has edited the content above our viewport which has left our viewport moved further down the document. This isn’t the default behaviour with collaborative editors, you will have to deal with this edge case specifically.

The same problem exists if content is resized/removed above your content, again knowing the caret position at modification is how we solve this however there is another gotcha..

The author is reading

So your author is reading content 5 lines above your current caret position and someone adds new content at the top of the document, you will not want to move the users caret position to the new location. I call this problem the “viewport war” problem.

How we solve this problem

Part 1: Is the caret in viewport range at all(If not we can assume the user is reading text elsewhere in the pad so don’t need to do anything)?

Part 2: If a new change above would modify the viewport to the point where the caret would no longer be visible for the user then always put the Offset Top at Caret Offset Top – (ViewPort height / 2). This means that the Caret is in the middle of the Y portion of the Viewport.

Highlighting

During highlight we should never allow the viewport to be moved by an edit event, only the user should be able to change it however this poses a new problem.. Imagine we are highlighting lines 5 to 10 and someone deletes lines 0, our editor would now look something like this..

|----------------|
| |------------| |
| |  document  | |
| |            | |
|-|            |-|
|-|------------|-|

Note the line above the document, the document is now below the top of our viewport.

How we solve this problem

This is another issue you will have to deal with in your editor. The best way is to basically stop the viewport moving if the content is being highlighted, this means that you allow the document (note we say document and not viewport) Offset Top to actually be greater than 0.

So to summarize

* Don’t allow edits above the viewport to move the viewport out of the visible caret location unless the caret is already outside the visible caret location.
* Don’t allow user changes to move viewport when author is highlighting

Anyway I wrote this so I can refactor the caret and viewport in Etherpad, a free and open source really-real time collaborative editor.

How to find out the version of an Etherpad instance?

The git hash is stored in the HTTP Response headers.

red

Open your browser (ff/chrome)
F12 (developer tools)
Network
Look in the header responses. It will show the Git Hash of the current revision then look at http://github.com/ether/etherpad-lite to see which hash is relevant to which version.

Get Etherpad Pad contents in Javascript

function getElementByIdInFrames(id, base) {
  var el;
  if(el = base.document.getElementById(id)) {
    return el;
  }

  for(var i = 0, len = base.frames.length; i < len; i++) {
    if(el = getElementByIdInFrames(id, base.frames[i])) {
      return el;
    }
  }
}
getElementByIdInFrames("innerdocbody", window).innerHTML;

This means you can also do…

getElementByIdInFrames("innerdocbody", window).innerHTML = "Superducky";

And if you want to use jQuery a one liner will sort you out..

console.log($('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody"));

NOTE: Due to a change in the jQuery API a . after iframe was removed — 10/02/2013

Means you can do..

$('iframe[name="ace_outer"]').contents().find('iframe').contents().find("#innerdocbody").html("Superducky");

Thanks to Mark Fisher for the jQuery example