Fixed Netflix Autoplay Bookmarklet!

As promised, here is the new fixed version of my bookmarklet:

Netflix Autoplay Bookmarklet
Please note I can no longer tell when the episode is almost over, so I am now just timing the entire length of play. You will now see a countdown telling you when it will change to the next episode. This means a few things:
* If you pause the show, or it takes a long time to buffer, or is has to buffer in the middle, the timer will be off and it will switch early. There are some things you can do about this, first, you will see ‘||’ next to the timer, clicking it will pause the countdown, clicking again will unpause it, so if you walk away, be sure to click this. Also, you can click the countdown and type a new time into it, you can either give a number of seconds, or enter time in the mm:ss format.

Also, next to the pause button you will see a ‘-‘ most of you can ignore this, but if you have a lot of problems with buffering taking a really long time or something, you can also click the ‘-‘ and enter a number of seconds there. This will be added to what the bookmarklet thinks is the length of each episode to allow for buffering and such. By default 10 seconds is added to allow the episodes to load properly.

I have not done extensive testing on this, so there could very will be some bugs. If you find any, let me know.


(function(undefined){

//Takes our countdown timer and converts it to mm:ss and displays to user
function updateTime() {
var seconds = (countdownTimer % 60) + '';
if(seconds.length === 0)
seconds = '00';
else if(seconds.length === 1)
seconds = '0' + seconds;

timerNode.innerHTML = Math.floor(countdownTimer / 60) + ':' + seconds;
}

//Grabs the data for the episode matching the ID passed in
function getCurrentEpisodeData(episodeId) {
var episode, i = 0, j = 0;

if(episodeData && episodeData.video && episodeData.video.seasons) {
for(i=0; i<episodeData.video.seasons.length; i++) { for(j=0; j<episodeData.video.seasons[i].episodes.length; j++) { if(episodeData.video.seasons[i].episodes[j].id === episodeId) { episode = episodeData.video.seasons[i].episodes[j]; break; } } if(episode) break; } } return episode; } //Grabs the data for the episode following the one with the ID passed in function getNextEpisodeData(episodeId) { var episode, i = 0, j = 0, found = false; if(episodeData && episodeData.video && episodeData.video.seasons) { for(i=0; i<episodeData.video.seasons.length; i++) { for(j=0; j<episodeData.video.seasons[i].episodes.length; j++) { if(episodeData.video.seasons[i].episodes[j].id === episodeId) { found = true; } else if(found) { episode = episodeData.video.seasons[i].episodes[j]; break; } } if(episode) break; } } return episode; } //base64 decoder function decode64(input) { var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', output = '', chr1, chr2, chr3 = '', enc1, enc2, enc3, enc4 = '', i = 0, base64test = /[^A-Za-z0-9\+\/\=]/g; input = input.replace(base64test, ''); do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } chr1 = chr2 = chr3 = ''; enc1 = enc2 = enc3 = enc4 = ''; } while (i<input.length); return unescape(output); } if(window.NetflixAutoplayLoaded) { alert('Autoplay already loaded.'); //return false; } else { window.NetflixAutoplayLoaded = true; } //resize the player so we can see the text we are about to insert under it document.getElementById('SLPlayer').style.height = (window.innerHeight - 35) + 'px'; document.getElementById('SLPlayerWrapper').style.height = (window.innerHeight - 35) + 'px'; document.getElementById('page-content').style.height = (window.innerHeight - 35) + 'px'; //create the text and other shizzy we want var autoplayElement = document.body.appendChild(document.createElement('div')); autoplayElement.id = 'NetflixAutoplayContainer'; autoplayElement.innerHTML = '

Time left until switch: 00:00 || -
'; var timerNode = document.getElementById('NetflixAutoplayTimer'); var autoplayText = document.getElementById('NetflixAutoplay'); var pauseButton = document.getElementById('NetflixAutoPauser'); var delay = document.getElementById('NetflixAutoDelay'); //the number of seconds to wait for the show to buffer on load var timerDelay = 10; //Pull the current episode information and the full series data for us var episodeData = JSON.parse(decode64(netflix.Silverlight.MoviePlayer.getPlugin().settings.metadata)); var currentEppId = (/,EpisodeMovieId=\d*/.exec(netflix.Silverlight.MoviePlayer.getPlugin().settings.initParams)[0]).split('=')[1]; //gets data for current & next epps var currentEpp = getCurrentEpisodeData(currentEppId); var nextEpp = getNextEpisodeData(currentEpp.id); var paused = false; //bool to see if paused var done = false; //bool to see if finished autoplay var editingTime = false; //bool to check if user is editing time //Javascript to execute to change episode var ini = document.getElementsByTagName('script'); ini = ini[ini.length-1].innerHTML; //Set the countdown till next episode + the pause we need for buffer function updateCountdown() { countdownTimer = parseInt(currentEpp.runtime, 10) + timerDelay; } var countdownTimer = 0; //episode length timer updateCountdown(); //Sets the count down timer for current episode //Prompt user for number of episodes var numToWatch = 3; function getNumberOfEpisodesToWatch() { var newNum; do { newNum = prompt('How many episodes would you like to play?', numToWatch); } while (isNaN(newNum)); numToWatch = parseInt(newNum, 10); //set the text if(numToWatch > 0) { autoplayText.innerHTML = 'Netflix autoplay on, Episodes left: ' + numToWatch; if(done) { //if we have already finished, restart done = false; switchEpps(); } } else { autoplayText.innerHTML = 'Netflix autoplay off'; } } //ask the user for the number of episodes they want getNumberOfEpisodesToWatch(); //handler for pause button function pause() { if(pauseButton.innerHTML === '||') { paused = true; pauseButton.innerHTML = '>'; } else { paused = false; pauseButton.innerHTML = '||'; } } //handles editing the buffer delay function delayEdit(e) { if((!e.keyCode || e.keyCode === 13) && !isNaN(delay.innerHTML)) { timerDelay = parseInt(delay.innerHTML, 10); if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return false; } } //handler for editing time left to play function editTime(e) { timerNode.contentEditable=true; editingTime = true; if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return false; } //handles updating time left to play after user edit function endEdit(e) { if(timerNode.contentEditable.toString() === 'true' && (e.type === 'blur' || e.keyCode === 13)) { timerNode.contentEditable = false; editingTime = false; var time = timerNode.innerHTML; if(time.indexOf(':') >0) { //converts minutes:seconds into time time = time.split(':'); countdownTimer = parseInt(time[0], 10)*60 + parseInt(time[1], 10); } else { //converts just seconds into time if(!isNaN(time)) { countdownTimer = parseInt(time, 10); } } if (!e) var e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return false; } } //Does everything to change to the next episode function switchEpps() { //setup the script for the next epp ini = ini.replace(/,EpisodeMovieId=\d*/,',EpisodeMovieId=' + nextEpp.id); //switch out data to the new epps currentEpp = nextEpp; nextEpp = getNextEpisodeData(currentEpp.id); //Switch to next epp & update text if(currentEpp) { autoplayText.innerHTML = 'Netflix autoplay on, Episodes left: ' + numToWatch; updateCountdown(); eval(ini); nextEppTimer(); } else { autoplayText.innerHTML = 'There does not seem to be a next episode.'; } } //Main loop, updates our timer and stuff, switches to next epp when necessary, yadayda function nextEppTimer() { setTimeout(function() { if(isNaN(countdownTimer) || isNaN(numToWatch)) { autoplayText.innerHTML = 'NUMBERS DO NOT WORK THAT WAY! GOOD NIGHT!'; nextEppTimer(); return; } if(countdownTimer <= 0) { if(numToWatch-- > 0) { switchEpps(); } else { done = true; autoplayText.innerHTML = 'Netflix autoplay completed.'; numToWatch = 0; } } else { if(!editingTime && !paused) { countdownTimer--; updateTime(); } nextEppTimer(); } }, 1000); } //attach the events we need autoplayText.addEventListener('click', getNumberOfEpisodesToWatch, false); pauseButton.addEventListener('click', pause, false); timerNode.addEventListener('click', editTime, false); timerNode.addEventListener('blur', endEdit, false); timerNode.addEventListener('keypress', endEdit, false); delay.addEventListener('blur', delayEdit, false); delay.addEventListener('keyup', delayEdit, false); //START EVERYTHING! nextEppTimer(); })();

I Know My Netflix Bookmarklet Broken!

Yes folks, I know. I am sorry, the new player they released last week broke it. I too was sad to discover this, I mean I didn’t really make it for you all, I made it because I wanted it! I really didn’t know if anybody was actually using it, but seeings as my site has maintained traffic at pretty much double by highest day ever EVERY DAY since Netflix broke it, I guess other people like it too. Netflix changed a lot of things, some of it easy to fix, some not as much. Shit happens; que sera sera.

YES, I HAVE A FIX! It is not done yet. I think it will be better in some ways, worse in others. I promise you all (baring catastrophe) it will be out this week.

Thanks to everybody leaving comments and sending me emails telling me that you love it, were upset that it broke, and really want me to fix it! It is amazing to have people actually getting enough enjoyment out of something I made to do those things!

TL;DR: Fix will be out this week.

Getting All jQuery Events for a DOM Node.

I have more than once found myself needing to know all of the events for a specific DOM node. In the past I have used which worked great for a time. The problem is that I am now working on projects so large that Visual Events takes forever to load and then fills the entire screen making it nigh impossible to find the events for the node I am interested in. Combine this with live events, delegated events, on/off, etc, Visual Events was no longer doing the job for me. So I made myself another bookmarklet.

jQuery Events

To use it:

Drag the link to your bookmark bar.

Open the page you wish to see the events on.

Open your javascript console.

Click the bookmarklet.

Click the little red “Click Me” it puts in the top left corner.

Click the element you wish to see the events for.

In your console you will see a list of all of the events for the node you clicked, what type they are, and their namespace. If the node you click has no events (perhaps you clicked an element inside of the one with the events) it will look up the DOM until it finds a node with events. It will also look all the way up the DOM for that node and show you all of the events that are delegated to that node. The code could use a lot of cleanup and stuff, but it works for me right now.

Here is the code for it:


javascript:(function() {
$('#eventFinder, #eventTitle').remove();

$(document.body).append('




');

$(document.body).append('

Click Me
'); $('#eventTitle').click(function() { $('#eventFinder').show(); }); function findNodeEvents(e) { $('#eventFinder').hide(); var ele; if(e.pageX) { ele = document.elementFromPoint(e.pageX, e.pageY); } else { ele = e[0]; } var tmpEvents; var $origEle = $(ele); var $ele = $origEle; var events = []; var eventsFound = false; console.log('Finding events for node:'); console.log($origEle); while(true) { tmpEvents = $ele.data('events'); if(tmpEvents) { for(type in tmpEvents) { for(var i = 0; i < tmpEvents[type].length; i++) { if(((!tmpEvents[type][i].selector && $ele[0] === ele) || $origEle.is(tmpEvents[type][i].selector))) { eventsFound = true; if($ele[0] !== ele) { console.log('Delegated From:'); console.log($ele); } console.log('Event: ' + type + (tmpEvents[type][i].namespace ? '.' + tmpEvents[type][i].namespace : '') + ': ' + tmpEvents[type][i].handler); } } } } $ele = $ele.parent(); if($ele.length <= 0) { break; } } if(!eventsFound) { var $parent = $origEle.parent(); if($parent.length > 0) { console.log('No events found - Trying parent'); findNodeEvents($parent); } else { console.log('No events found - Do you know what you are doing?'); } } }; $('#eventFinder').click(findNodeEvents); })();

I Githubed myself!

(Githubbed?) Well, I finally took the time to throw my bookmarklet up on Github, I have some more projects I may add soon, but for now, if you want to fork my bookmarklet, I have it here:

https://github.com/rtpmatt/Netflix-Autoplay-Bookmarklet

Also, getting on Github with windows was not as easy as they made it sound, I had to generate keys using puttygen, doing it through GIT bash did not work at all like Ghithub’s documentation lead me to believe.

Fixing the Terrible: Turning an SVN Branch into a GIT Repository

Long ago, someone, for some reason, thought it was a good idea to use branches in SVN as if they were repositories, so one repository contained many different branches each branch being its own completely independent project. When we moved to GIT we agreed to not repeat this mistake. We converted the entire SVN repo to a GIT repo long ago using Svn2Git, this worked well, except we still had all of those crazy branches in there. I don’t remember how we got the other branches out originally, but I am sure it was not the best way…or even a good way. We have been using GIT for some time and understand it better, and we still have a few old branches that we never extracted, but the time has come for us to do so. It was much easier now that we have some clue what is going on, and I though I would explain how we did it because it is rather simple…if you know what you are doing.

Overview:
For us we actually want these to be bare remote repositories can be pushed to. Basically all you need to do is push the single branch into a new GIT repository and create a “master” branch in it. If you are making a bare repository, you then just need to clone the new repository to a bare one. Really, that is it, so here is a breakdown.

Breakdown:
So, working on the assumption that we have our entire SVN repo (or whatever is your poison) converted to a GIT repo, we start by creating a new empty repo. Create a new directory to hold the repo, then go into that directory and run:

git init

This creates a new empty repository.

Now, push the branch from the old repo into the new one by going into the directory of the old repo and running:

git push /path/to/new/repo branch-to-extract

At this point your new repo contains only the single branch you extracted!

Next we have to create a new “master” branch for this repository. To do so, first we have to checkout the old branch. Navigate to the directory for the new repo you just created and run:

git checkout branch-to-extract

You should now see the code that was in that branch. Now we create a new “master” branch with:

git branch master

At this point you have a GIT repo for you project that is good to go! If this is all you need, you are done! If you want to turn this into a bare remote repository that someone can use, simply clone it –bare as normal.

Simply clone this new repo to the new remote one:

git clone --bare /path/to/new/repo /path/to/bare/repo

You can now clone this repo to your local machine and use it normally, with all the great pushing, pulling, branching and everything else GIT has to offer. If you like you can delete the “/path/to/new/repo” repo that we setup to extract old branch from, or delete the branch that you imported from from your fantastic new repo.

It really is that easy. It all makes perfect sense now and took me only a few minutes to figure out. When we were first trying to move to GIT though, and didn’t really understand it, this was not easy and did not make perfect sense. Hopefully if someone else out there has done the same bad thing or something similar and is now trying to fix the problem this will help them out.

Netflix Autoplay Bookmarklet – With Multiple Season Support!

Not content to leave well enough alone, I was hacking on my bookmarklet some more. It can now handle crossing between seasons. There is also a bit less guessing going on now, so I am more confident in it.

For the record, I want to say, I hope Netflix does not get upset about this, if they want me to remove it I will.

To use this:
1. Drag the link below on to your bookmarks bar.
2. Start watching a TV show on Netflix.
3. Click the bookmark and enter then number of episodes you want to watch.

Note: I have now tested this in IE. It does NOT work. But you wouldn’t use IE anyway, would you?

Netflix Autoplay Bookmarklet – With Multiple Season Support![/raw]


javascript:(function(netflix, undefined) {
var seasonId = 0,
episodeId = 0,
numWatched = 0,
numToWatch = 3,
epIdRegex = /,EpisodeMovieId=\d*,/,
idregx = /\d+/,
done = false,
sl,
init,
currrentEpisodeId,
currentMovieId,
seasons,
showData,
waitTimer,
node;

if(!netflix || !netflix.Silverlight || !netflix.Silverlight.MoviePlayer || !netflix.Silverlight.MoviePlayer.getPlugin() || !netflix.Silverlight.MoviePlayer.getPlugin().settings.initParams) {
alert('You do not appear to have a show playing, please start a show first');
return;
}

//grab the things we need
sl = netflix.Silverlight.MoviePlayer.getPlugin().getScriptInterface();
init = netflix.Silverlight.MoviePlayer.getPlugin().settings.initParams;
currrentEpisodeId = parseInt(idregx.exec(epIdRegex.exec(init)), 10);
currentMovieId = parseInt(netflix.Silverlight.MoviePlayer.getPlugin().settings.movieId, 10);

//Check if the user has already loded teh bookmarklet
var autoplayElement = document.getElementById('NetflixAutoplay');
if(autoplayElement) {
alert('You have already loaded the autoplay bookmarklet, click the text at the botton to change number of episodes.');
return;
}

//grab the metadata and decode it
try {
showData = JSON.parse(decode64(netflix.Silverlight.MoviePlayer.getPlugin().settings.metadata));
} catch(e) {
alert('Error processing data =(');
return;
}

if(showData.Movie) {
alert('This appears to be a movie not a TV show. This bookmarklet only works on TV show.');
return;
}

//set our pointest to match the episode we are currently on
seasons = showData.Show.Seasons;
for(seasonId = 0; seasonId < seasons.length; seasonId++) { for(episodeId = 0; episodeId < seasons[seasonId].Episodes.length; episodeId++) { if(seasons[seasonId].Episodes[episodeId].MovieId === currentMovieId || seasons[seasonId].Episodes[episodeId].MovieId === currrentEpisodeId) { done = true; break; } } if(done) { break; } } //check if we were able to find the episode the user is on if(seasonId === seasons.length) { alert('Error: Already of final episode, or episode data could not be found.'); return; } //Prompt user for number of episodes function getNumberOfEpisodesToWatch() { var newNum; do { newNum = prompt('How many episodes would you like to play?', (numToWatch - numWatched)); } while (isNaN(newNum)); numWatched = 0; numToWatch = parseInt(newNum, 10); //set the text if(numToWatch > 0) {
autoplayElement.innerHTML = 'Netflix autoplay on, Episodes left: ' + numToWatch;
} else {
autoplayElement.innerHTML = 'Netflix autoplay off';
}
}

//create the text that shows how many episodes left & insert it
node = document.createElement('span');
autoplayElement = document.body.appendChild(node);
autoplayElement.id = 'NetflixAutoplay';
autoplayElement.innerHTML = 'Netflix autoplay on, Episodes left: ' + numToWatch;

//attach a click handler so people can change number of episodes
autoplayElement.addEventListener('click', getNumberOfEpisodesToWatch, false);

//prompt the user for number of episodes for the first time
getNumberOfEpisodesToWatch();

//handle when the episode ends
sl.OnMovieWatched = function() {
if(numWatched < numToWatch && !waitTimer) { //Check if done autoplaying waitTimer = setTimeout(function() { //Set our timer so we do not end early var epp, numLeft; //move episode/season counters properly if(seasons[seasonId].Episodes[episodeId+1]) { episodeId++; } else { episodeId = 0; seasonId++; } //if there is a next episode, grab it if(seasons[seasonId] && seasons[seasonId].Episodes[episodeId]) { epp = seasons[seasonId].Episodes[episodeId]; } //if there is a next episode, play it and update the text if (epp) { sl.PlayMovie({movieId: epp.MovieId, episodeMovieId: 0, trackId: 0}); numWatched++; numLeft = numToWatch - numWatched; if(numLeft > 0) {
autoplayElement.innerHTML = 'Netflix autoplay on, Episodes left: ' + numLeft;
} else {
autoplayElement.innerHTML = 'Netflix autoplay completed.';
}
}

//cleanup
clearTimeout(waitTimer);
waitTimer = null;
}, 2*60*1000);
}
};

//This is just a bse64 decoder
function decode64(input) {
var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
output = '',
chr1, chr2, chr3 = '',
enc1, enc2, enc3, enc4 = '',
i = 0,
base64test = /[^A-Za-z0-9\+\/\=]/g;

input = input.replace(base64test, '');

do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));

chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } chr1 = chr2 = chr3 = ''; enc1 = enc2 = enc3 = enc4 = ''; } while (i<input.length); return unescape(output); } })(window.netflix); 

In addition to handling moving between seasons, it now also adds a little text below the movie to tell you that it is on. You can click this text to change the number of episodes it will play. Entering 0 for the number of episodes will turn this off.

Netflix Autoplay Bookmarklet!

I have updated this plugin, please use the new BETTER version – Click Here

Netflix is awesome, most of the time the fact that I have to click the “Play next episode” button does not bother me, usually I am at my computer anyway. But, if you are like me, you enjoy throwing on a show (say Futurama) and having a few episodes play while you go to sleep. The ~22 minutes an episode is just not enough, I like to have about 3 play. Netflix does not have a way for me to do this. I thought to myself “How hard could this be?” and spent the rest of the night throwing this baby together:

Netflix Autoplay Bookmarklet
If you don’t know how bookmarklets work, simply drag the above link onto you link bar.

To use it, just start an episode of the show you want to watch, click the bookmark, enter the number of episodes you want it to play, then just let it run. It should keep playing episodes, until either the season ends, or it reaches the number you entered.


javascript:(function(){
	var eppsWatched = 1,
		movieIDrgx = /movieid=\d*[&|#]/,
		trackIDrgx = /trkid=\d*[&|#]/,
		episoIDrgx = /episodeMovieId=\d*[&|#]/,
		idregx = /\d+/,
		epps, movieId, trkId, eppId, sl, tmp;

	//grab netflix JS interface for player
	try {
		sl = netflix.Silverlight.MoviePlayer.getPlugin().getScriptInterface() || false;
	} catch (e) {
		sl = false;
	}		

	if(!sl) {
		alert('Please start the first episode you wish to watch.');
	} else {
		epps = prompt('How many episodes would you like to play?', '3'),
		//number of episodes the user wants to watch
		epps = parseInt(epps, 10);

		//extract the IDs from the url
		movieId = parseInt((idregx.exec(movieIDrgx.exec(window.location)) || [0])[0], 10);
		eppId 	= parseInt((idregx.exec(episoIDrgx.exec(window.location)) || [0])[0], 10);
		trkId 	= parseInt((idregx.exec(trackIDrgx.exec(window.location)) || [0])[0], 10);

		//add even to movie finished
		sl.OnMovieWatched = function() {
			var waiting = null; //make our timeer

			//check if we have finished watching
			if(eppsWatched < epps) {
				//move to the next episode
				if(trkId !== 0) {
					trkId++;
				}
				if(eppId !== 0) {
					eppId++;
				}
				if(movieId !== 0) {
					movieId++;
				}			

				if(!waiting) { //this event triggers about 2 minutes before the end of a show - and multiple times, my guess it is how they move their index to know they should start with the next epp next time
					waiting = setTimeout(function() {
						//tell netflix to play the next epp
						sl.PlayMovie({movieId: movieId, episodeMovieId: eppId, trackId: trkId});

						//increment our episode counter
						eppsWatched++;

						clearTimeout(waiting);
						waiting = null;
					},2*60*1000);
				}
			}
		}
	}
})();

Some caveats:

  • This (obviously) only works with shows, not movies, because there needs to be a "next episode".
  • This only works within a single season, so if you start with the last episode of a season, you will only ever see that single episode.
  • The movie ID seems to be the only one that matters, I haven't even seen the episodeMovieId used anywhere, I am incrementing all the IDs, it has worked for the 3 shows I have tried.
  • Netflix executes their "OnMovieWatched" event before the end of the movie, and I am not exactly sure how far before, so I just kind of guessed a bit.
  • This works for me in Chrome and Firefox, if you are using some other shitty browser, or an old version and it does not work for you, I don't care. Use a better browser.

    There is a good chance this does not work with all shows. In fact I would be shock if it worked on all shows. If you find a show it does not work on, feel free to let me know and I might take a look! If you want to fix it yourself and send me the update, even better!

    So, Netflix could easily stop this from working, but I hope they don't. Fortunately, probably only about 3 people read my blog, so I think this should be pretty safe!

    How did I figure this out? Well, I don't want to type that up right now, so it will have to wait for another post.

    I don’t GIT rebase.

    I switched my team over to GIT about a month ago. For the most part it has been ok, a few small problems here and there, a lot of learning. We ran into a situation that caused us quite a headache recently with changes that are not to go live being already in master. We had this same problem when using SVN, so it was not new, just crappy, as it always is. We had spent the last month however playing with GIT and we were all now very clear on how easy branching is in GIT. We decided at that point that we would adopt a more GITy approach to development and create new branches for all changes then only ‘merge’ them into master when they had been completed. This is when I started to do more research on rebasing…

    Note: I want to point out, this is about rebasing between branches from a remote repo that multiple people are could be working on. Rebasing in a single branch is/with only local work is…well, read about it somewhere else. It is easy.

    What is rebase?  What does it do?

    Rebase is like a merge, the difference is that it preserve the commit history. As Shakespeare once wrote, “Words, words, words!”. Here:

    So, that is really it, instead of having a single “merge commit” you get the entire commit history.

    Why would you want this? Well, our workflow is to work on a new feature (or bug fix) in a new branch, then when it is done move those changes into master and delete the branch. The problems is if you just use merge, you will end up with a master that is nothing but merge commits, so its revision history is pretty worthless. With rebase, we can get rid of the branch, but not lose its history. As far as we are concerned, once the feature is done, it is just “part of the projects past” and we don’t want to dig down into old broken branches if we need to go back to it.

    Sounds cool, but someone else already rebased their changes into master? / OH GOD EVERYTHING IS BLOWING UP

    Ah yes, this is where it gets a little tricky. So, say we have a team of multiple people, all working on their own branches, someone finishes their work, rebases it into master, now my branch is not coming off of origin/master…this does not seem good.

    Yeah, so I wish I had a picture for this, but I started making one and I got tired.

    You have 2 choices at this point.

    You can rebase master back into your branch

    This is what I tried first. This shoves all of the changes that were added to master to the bottom of your branch. This means your local branches commit history has changed and if you try to push, you will get an error. Basically, your local branch now looks like a totally different branch from what is in origin, this is bound to cause problems. You can do a “force push” which will basically says ‘screw what is in origin, use this’ which works, it is just crappy for anyone else working on that branch because everything goes a bit wonky.

    Note: People have been questioning me about what I said above. If your branch is only local (ie: has never been push, only committed to) you don’t have to force push because it does not exist in origin yet, so origin can’t complain. Also, if you are the only person who has done any work on the branch, doing a force isn’t really all that bad as you can only hurt yourself. I unfortunately do not have the luxury of working in an environment where either of these is usually the case.

    You can merge master into your branch

    This seems to be the way to go. Doing this means your commit history stays the same and you just get the changes that were added to master after you branched as a ‘merge commit’. Now, when your branch is done and you rebase back into master, you are already up to date with what is there, and it can easily add the commits from your branch on to the top of master.

    This won’t look as pretty as always rebasing. If you rebase it makes it appear that your branch is always coming off of the latest version of master. If you merge, your branch will come off of master, then come back together where you merge, and basically flow in and out as you merge/make changes. But then again, we are planning on deleting the branch when it is done anyway, so who cares if it does not look super pretty…Also, I guess as long as the workflow is good, I should not be caring about the prettyness of my repo, stupid brain!

    Recap

    Workflow: when you start working, you create a branch off of master and make your changes. If changes are made to master (say someone finishes the changes in their branch and rebases it into master) while you are working, merge them back into your branch. When your branch is done, rebase it into master and delete the branch.

    Basically, you only ever rebase master. Other branches should have changes from master merged into them.

    Is this the commonly accepted workflow? Uh..sure…why not? It is here. I would change it if someone proposed something better. Like I said, I have spent a lot of time reading about these things, there is a lot of information…I just don’t see a lot of information that answers the question I had when trying to figure all of this out.

    I wrote this up because even though there is tons of information about all these things online, nobody seems to provide it in a manner that was clear to me until I spent a good deal of time playing with it myself…I have probably not really helped that either.

    PS: Yes, I am very well aware of how lame and I am sure over-done the joke (if you can even call it that) in my title is. I am lame.