Let's prettify the block and break it down:
{
  function foo() {};
  foo = 1;
  function foo() {};
  foo = 2;
}
console.log(foo); // 1
Block-scoped function declarations generally have their variable name hoisted out of their block (with a value of undefined), but only receive a value once inside the block. You can see more details on that behavior here.
But this situation is a bit different - you're declaring a function twice in a block. It looks like the first foo function gets assigned to the name that's visible outside the block. You can see this if you log the property descriptor on the window at different points in the code.
It looks like, once the duplicate function declaration is reached, further references to the foo variable name inside the block refer to a binding only inside the block. So, the foo = 2 at the bottom, since it's after the duplicate declaration, only results in the 2 value being bound to the foo name when referenced inside the block. The value outside the block remains the value that foo last held before the duplicate function declaration:
// Variable name is hoisted: it exists on the global object, but is undefined
console.log(Object.getOwnPropertyDescriptor(window, 'foo'));
{
  // Function declaration: global foo gets assigned to
  function foo() {};
  console.log(window.foo);
  // Assignment to foo name: global foo gets assigned to
  foo = 1;
  // Assignment to foo name: global foo gets assigned to
  foo = 3;
  console.log(window.foo);
  // Duplicate function declaration: past this point, foo now no longer refers to the global foo
  // but to a locally-scoped identifier
  function foo() {};
  // See how the window value remains at 3:
  console.log(window.foo);
  // So this assignemnt only changes the binding of the `foo` inside this block
  // while window.foo remains at 3:
  foo = 2;
}
console.log(foo); // 3
 
 
Another way of looking at the original code:
{
  function foo() {}; // this ALSO does: window.foo = function foo() {};
  foo = 1; // this ALSO does: window.foo = 1
  function foo() {};  // this actually does: fooLocal = function foo() {};
  // further references to "foo" in this block refer to fooLocal
  foo = 2; // this actually does: fooLocal = 2
}
console.log(foo); // references window.foo