GraphQL is a query language.
See the quick introduction in the docs but, essentially, we are working with
associatedPullRequests
is a connection between a branch (ref) and a pull request.pullrequest
objectHere is the query we are using to find which PR(s) are associated with a given commit (only using the first one found so far):
{
repository(name: "example-repo", owner: "ftclausen") {
ref(qualifiedName:"topic/some_branch") {
associatedPullRequests(first: 1) {
edges {
node {
number
title
baseRef {
name
}
}
}
}
}
}
}
This then returns the following data
{
"data": {
"repository": {
"ref": {
"associatedPullRequests": {
"edges": [
{
"node": {
"number": 55,
"title": "This is an example pull request",
"baseRef": {
"name": "main"
}
}
}
]
}
}
}
}
}
As best I understand edges
will always be an array. Finally this can be imagined graphically which I find useful:
We then pull the above data in and use it as needed
Given GraphQL data varies per type I followed this very helpful guide guide to generate types for GraphQL responses instead of manually doing it. Please see that for a concrete example.
We have three different ways pull requests are represented depending on the context:
So that is something to be aware of when working with TypeScript. I had to make my own meta-object to unify this for my purposes; this meta-object extracted just what I needed out of each one of the above types.
]]>Note on book context: Page 15, from just before equation 1.15.
About re-using the cyclical bit-shifting “magical solution” to the Josephus problem in a more generalised context. Firstly, the magical solution they are talking about:
\[J((b_mb_{m-1}...b_1b_0)_2) = (b_{m-1}...b_1b_0b_m)_2\]They then ask
Does the generalised Josephus recurrence admit of such magic? Sure, why not?
They then go on to, very cleverly, rewrite generalised equation in a new form that allows us to look at it in a different way. Firstly, the original generalised equation 1.11:
\[\begin{align} f(1) &= \alpha \\ f(2n) &= 2f(n) + \beta\text{,} \ \ \ \ \ \ \text{for } n \geq 1\text{;} \\ f(2n+1) &= 2f(n) + \gamma\text{,} \ \ \ \ \ \ \text{for } n \geq 1\text{.} \ \end{align}\]Then the rewritten one as 1.15
\[\begin{align} f(1) &= \alpha; \\ f(2n + j) &= 2f(n) + \beta, \ \ \ \ \ \ \text{for}\ j=0,1\ \text{and}\ n\geq 1 \\ \end{align}\]Now they say
If we let $\beta_0 = \beta$ and $\beta_1 = \gamma$
Which I think means: if we encounter a binary $0$ ($\beta_0)$ in the number then we substitute the $\beta = -1$ from the generalised form 1.11 equation. Otherwise, if we encounter a binary $1$ ($\beta_1$) then we substitute the $\gamma = 1$ also from the generalised equation 1.11.
It is unfortunate that they overload $\beta$ in the generalised equation 1.11, rewritten generalised equation 1.15, and in the expansion notation $\beta_m$ where $m$ is the position/power of two in the binary number. I think that is the root of my confusion.
The table presented on page 16 (of the physical book, second edition) is very enlightening in demonstrating the rewritten (1.15) equation is used:
\[\begin{array} n &= &(&1 &1 &0 &0 &1 &0 &0 &)_2 &= 100 \\ \hline f(n) &= &(&1 &1 &-1 &-1 &1 &-1 &-1 &)_2 \\ \end{array}\]Where:
Applying above wherever we find a binary 1 convert to decimal with the appropriate power of two $m$ as in $2^m$ then multiply by $1$. Likewise, wherever we find a binary 0, convert to decimal with appropriate power of two $m$ as in $2^m$ then multiply by $-1$. Doing the above leads us to:
\[+64 +32 -16 -8 +4 -2 -1 = 73\]So J(100) = 73
]]>Thus I settled on typed-rest-client which seemed relatively straight forward to use.
I’ll show some snippets of how I fetched a Work Item and then how to update a custom field (or any field really). For my
use case I just created a toy type to store just what I was interested in rather than try and pull down an official type
from the monster azure-devops*
repos above (🐇). If I find a more “official” way of doing it then I’ll update this
post.
Given the labynth of types azure-devops-node-api and azure-devops-extension-api have I decided to just make my own as follows:
export interface ADOWorkItemLite {
readonly id: number,
readonly fields: ADOWorkItemFields
}
export interface ADOWorkItemFields {
"Custom.SomeField": string // Can also be another, non-custom field
}
// Note: I can probably get the PAT handler from type-rest-client as well
import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'
const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
.RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])
/**
* Where:
* myorg - Your organisation name
* project - Your URL escaped project name
* 1234 - The ADO ticket being requested
* 7.0 - The ADO API version (7.0 as of March 2023)
*/
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'
return client.get<ADOWorkItemLite>(location).then(response => {
const workItem = response.result as ADOWorkItemLite
return workItem
})
For this I introduced a new type, the HTTP PATCH update payload:
export interface ADOWorkItemUpdatePayload {
/**
* What we're updating, usually "add"
*/
op: string,
/**
* The Work Item path e.g. /fields/SomeField
*/
path: string,
/**
* The value given to the item on the path
*/
value: string
}
Then the initial parts are the same as getting a work item above but, after that, we apply the update:
import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'
const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
.RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'
const patchContents: ADOWorkItemUpdatePayload = {
op: "add",
path: "/fields/Custom.SomeField",
value: updatedWorkItem.fields['Custom.SomeField']
}
return client.update<ADOWorkItemUpdatePayload>(location, [patchContents], {
additionalHeaders: {
"Content-Type": 'application/json-patch+json'
}
}).then(results => {
console.log(`${results.statusCode} - Updated work item ${updatedWorkItem.id}`)
})
For me, this worked. As mentioned I should use the auth handler from type-rest-client to remove the dependency on azure-devops-node-api.
]]>In this example they propose the problem of
Write numbers using each of the ten digits exactly once so that the sum of the numbers is exactly 100
Then the book goes through some attempts where we relax the conditions.
Here are some examples from the book where they try to get close by relaxing the condition that they need to add up to 100:
\[19 + 28 + 37 + 46 + 50 = 180\]or
\[19 + 28 + 30 + 7 + 6 + 5 + 4 = 99\]close but no cigar.
Here we get 100 but we are repeating some numbers. It would seem it is not possibles to satisfy both conditions (uniqueness and adding up to 100) at once.
We need to prove that it is impossible to solve both conditions at the same time.
Now we go about being optimistic and try to satisfy both conditions. The book does this by being very clever I thought; simply pretend both conditions are true even though we suspect they are not.
Some ideas:
Now comes the bit I found confusing:
Some of these figures denote units and some of them tens
I thought they meant in the above some of single figures. They were all units but then I realised the author meant while adding to get 100 not in the sum to make $45$. Continuing:
It takes a little sagacity to hit upon the idea that the sum of the figures denoting tens may be of some importance.
This also caused me some confusion (a common state for me to be in) but using the examples above:
\[19 + 28 + 30 + 7 + 6 + 5 + 4 = 99\]Here the sum of the tens figures are
\[10 + 20 + 30\]From the numbers 19, 28, 30. Another example from above:
\[19 + 28 + 31 + 7 + 6 + 5 + 4 = 100\]Where the tens are, again,
\[10 + 20 + 30\]We can see these give us the most progress to 100 (i.e. to 60). The others (part of the 45) get us the rest of the way. Now $60 + 45 = 105$ which is obviously too much so we need to subtract the ones we’ve already used to get to 60. This, then, finally brings us to:
\[10t + (45 - t) = 100\]It takes a little sagacity to hit upon the idea that the sum of the figures denoting tens may be of some importance. In fact, let $t$ stand for this sum. Then the sum of the remaining figures, denoting units, is $45 - t$. Therefore the numbers in the set must be
Breaking that down
So, altogether, this should add to 100.
Solving for $t$ we get
\[t = \frac{55}{9}\]Here we get the absurdum beacause, of course, $t$ should be an integer otherwise it won’t add to 100. Thus we can’t satisfy both conditions at once.
]]>In How to Solve It they go through a series of equivalent problems to find the eventual solution. So we may not know the solution to our original problem A but we can go through B, C, D, etc. until we find a solution we know or that can be directly “seen”.
The book goes through solving this equation with the unknown $x$
\[x^4 - 13x^2 + 36 = 0\]Since I am bootstrapping my mathematical knowledge I found the book’s treatment a bit terse without many explanations of how they got to each step. I eventually figured out that they are trying to massage the equation into a state where you can apply the perfect square rule. This makes things much simpler to reason about and thus find the solution.
As mentioned above this is
\[x^4 - 13x^2 + 36 = 0\]And our task is to find $x$. Or, more specifically, where the graph intercepts the $x$ axis at $0$. The steps are essentially a set of quadratic equation equivalence conversions.
My slow self needed to ponder this one for a while. In any case, B ends up being
\[(2x^2)^2 - 2(2x^2)13 + 144 = 0\]This had me baffled for a while but I, eventually, saw that they are multiplying by increasing powers of two. I don’t know if this technique has a name or how they arrived at doing this first step but, to write it another way,
\[\begin{align} 2^0\cdot(2x^2)^2 - 2^1\cdot(2x^2)13 + 2^2\cdot36 &= 0 \\ (2x^2)^2 - 2(2x^2)13 + 144 &= 0 \end{align}\]For those who know quadratics quite well can probably see where they are going with these steps but, anyway, I was still baffled at this stage but at least it could see the manipulation easily enough:
\[\begin{align} (2x^2)^2 - 2(2x^2)13 + 144 + 25 &= 0 + 25 \\ (2x^2)^2 - 2(2x^2)13 + 169 &= 25 \end{align}\]This makes things much more compact through, as I eventually figured out, the perfect square rule! To remind the forgetful like me it is: $(a - b)^2 = a^2 - 2ab + b^2$. There is also a $a + b$ version.
\[\begin{align} (2x^2)^2 - 2(2x^2)13 + 169 &= 25 \\ (2x^2 - 13)^2 &= 25 \end{align}\]The more compact form gives us a better handle on things. This is a key step to solve this more easily. The following steps are using this step to find the four solutions.
Here we take the square root and find a sort-of solution that still needs some work. Let’s call it the IKEA solution:
\[\begin{align} (2x^2 - 13)^2 &= 25 \\ 2x^2 - 13 &= \pm5 \end{align}\]Let’s isolate the $x^2$ like it has covid:
\[\begin{align} 2x^2 - 13 &= \pm5 \\ x^2 &= \frac{13\pm5}{2} \end{align}\]Now we take the square root remembering our $\pm$:
\[\begin{align} x^2 &= \frac{13\pm5}{2} \\ x &= \pm\sqrt{\frac{13\pm5}{2}} \end{align}\]Now can see what the concrete answers are: $x = 3$ or $-3$ or $2$ or $-2$.
]]>High level the steps are
Usually an up to date ca-certificates package will have a file containing all root certs. This package is available on Homebrew or on Linux. E.g. it could be in:
<homebrew base>var/homebrew/linked/ca-certificates/share/ca-certificates/cacert.pem
/etc/ssl/certs/ca-certificates.crt
Once you have the ca-certificates run
cat CA.crt /usr/local/homebrew/var/homebrew/linked/ca-certificates/share/ca-certificates/cacert.pem > allcerts.crt
Where CA.crt
is your CA intermediate certificate, be sure to also check the
path to the ca-certificates
full CA certificate list. It will probably be
different to above.
Then create a PKCS12 file containing your own cert as well as the CA chain:
openssl pkcs12
-export -in example.com.crt \
-inkey ~/somewhere/safe/example.key \
-out example.p12 \
-name <some alias> \
-CAfile allcerts.crt -caname root \
-chain
Replace the example names as appropriate and also <some alias>
to something meaningful. E.g. if using Tomcat this is commonly set to tomcat
.
keytool -deststoretype JKS \
-importkeystore \
-srckeystore example.p12 \
-srcstoretype PKCS12 \
-srcstorepass changeme \
-alias tomcat # Or any other alias that makes sense for your use case \
-deststorepass changeme \
-destkeypass changeme \
-destkeystore example.keystore
Note - keytool
complains if you use JKS due to it being deprecated but it has the greatest compatibility e.g. with Gradle.
First save the current secret (if there is one) so that it can be referred to later if need be
kubectl get -o yaml secret example-keystore > ~/somewhere/safe
Then delete the current secret (coordinating as appropriate if this is a heavily used one)
kubectl delete secret example-keystore
Because you cannot update secrets in-place.
kubectl create secret generic example-keystore --from-file example.keystore
Once this is done systems (like services in K8s) that mount in that secret volume will be able to use the updated cert. Deployments may need scaling down and then up again or just delete the pods so they are recreated.
Thanks to these for helping me along my way:
See the book for full context; I’ll just cover the induction related steps.
The recurrence comes in two flavours: odd and even. The complete recurrences are thus:
\[\begin{align} J(1) &= 1;\\ J(2n) &= 2J(n) - 1, \ \ \ \ \text{for n} \geq 1\\ J(2n+1) &= 2J(n) + 1, \ \ \ \ \text{for n} \geq 1\\ \end{align}\]And the closed form is as follows
\[J(2^m+\ell)=2\ell+1\]The book goes on to give an example of the even induction
$J(2^m+\ell)=2J(2^{m-1} + \ell/2) - 1 = 2(2\ell/2 + 1) - 1 = 2\ell + 1$
by (1.8) and the induction hypothesis; this is exactly what we want.
What confused me at first was the leap
\[2J(2^{m-1} + \ell/2) - 1 = 2(2\ell/2 + 1) - 1\]This is the key induction step - this is not done via algebraic manipulation. We are substituting the closed form that is equivalent to $J(n)$. Our closed form proposal is actually for $2J(n)-1=2l+1$ so we need to refit it to be for $J(n)$. Thus $l$ is in the previous power of two block (remember $J(n)$ not $2J(n)$) and half as big: $l/2$. So the $J(n)$ closed form is $2l/2 +1$.
So we plug that in and then he next step, however, is done via algebra:
\[2(2\ell/2 + 1) - 1 = 2\ell + 1\]Completing the even induction.
Credit: I was stuck on this until helped out by the ever helpful Brian M. Scott on Math Stackexchange. I will repeat that here and then do my own (simplified) take on thereafter. First Brian’s reply:
Your’re not applying the recurrence for the odd case correctly. Suppose that $2n + 1 = 2^m + \ell$, where $0 \leq \ell \leq 2^m$. The recurrence is $J(2n + 1) = 2J(n) + 1$, and n here is $\frac12(2^m + \ell - 1)$, so
\[\begin{align} J(2^m + \ell) &= 2\left(2^{m-1} + \frac{\ell - 1}{2}\right) + 1 \\ &= 2\left(\frac{2(\ell - 1)}{2} + 1 \right) + 1 \\ &= 2\ell + 1 \end{align}\]as desired
I will do some colour highlighting and ever more verbose explanation for my future self here. The core is we want to convert $2n + 1$ into $n$ hence dividing by two and subtracting one:
\[\frac{2(n + 1)}{2} - 1 = n\]We apply these operations during the induction step to turn $2\ell + 1$ into $\frac{2(\ell -1)}{2} + 1$. Notice the division by two and subtraction by one? And now with gusto and in context of the induction step where we “insert” the closed form representing n into the recurrence “framework” (substitution highlighted in blue):
\[\begin{align} J(2^m + \ell) &= 2\left(\color{blue}\frac{2(\ell - 1)}{2} + 1\color{#3d4144}\right) + 1 \\ &= 2\ell + 1 \end{align}\]Giving us what we want.
]]>Maths is my friend in the recovery process even though it is challenging. Perhaps because it is challenging.
This post goes over the recurrence only because I found that challenging. For some reason I find recursion harder than it should be 😅
While the concept of shooting each second remaining person sounds obvious I found it useful to step through it on pen and paper which I will attempt to reproduce here in text; really an animation is also useful to look at such as this one. I won’t show a diagram because all you see is the end result which is not so useful
I was accidentally thinking the dead person was also shooting someone (😅) which meant I was messing up above. Here is a tabular form of the above as presented in the book:
\[\begin{array}{|c|c|} \hline n & 1\ \ 2\ \ 3\ \ 4\ \ 5\ \ 6\ \\ \hline J(n) & 1\ \ 1\ \ 3\ \ 1\ \ 3\ \ 5\ \\ \hline \end{array}\]I think the point the book is trying to make is that, at each go around, we are essentially at the same point as the round before except there are half as many people _and their numbers (aka “label” or “position”) have changed. Or, as Wikipedia puts it:
The first time around the circle, all of the even-numbered people die. The second time around the circle, the new 2nd person dies, then the new 4th person, etc.; it is as though there were no first time around the circle.
This refers heavily to this post by, the ever helpful, Brian M. Scott. I am only adding even more details for my addled brain to help it understand.
First the recurrences (for base case, odd, and even) which is the notational expression of the above sentences:
\[\begin{align} J(1) &= 1 \\ J(2n) &= 2J(n) - 1, \ \ \ \ \ \ \text{for} \ n \ge 1; \\ J(2n + 1) &= 2J(n) + 1, \ \ \ \ \ \ \text{for}\ n \ge 1; \\ \end{align}\]The post by Brian M. Scott shows the case for $n = 21$ but I found it useful to step through the small cases as well.
Formatting note: I am using a comma followed by some notes. The font for the
\text{...}
is not beautiful but serves the purpose.
This is the base case so $J(1) = 1$
Doing this manual unrolling really helps understand the nature of the problem when expressed in math notation. We can see how powers of two play a role which is useful in the closed for solution(s).
]]>This is equation 2.54 and, at first, I did not understand the second step especially the section highlighted in blue
\[\begin{align} \Delta(u(x)v(x)) &= u(x+1)v(x+1) - u(x)v(x) \\ &= u(x+1)v(x+1) \boldsymbol{\color{blue}{- u(x)v(x+1) + u(x)v(x+1)}}\color{#3d4144} - u(x)v(x) \\ &= u(x)\Delta v(x) + v(x+1)\Delta u(x) \end{align}\]My initial reaction was: wtf is the new terms above? Had me stumped for a while I must confess.. Then I realised they were just cancelling out extras conjured into existence like a quickly annihilating virtual particle/anti-particle in the quantum vacuum. These “nothing terms” are used for factoring to arrive at our difference-of-a-product form at 2.54 in the book. So, more verbosely, the factoring step is in blue this time:
\[\begin{align} \Delta(u(x)v(x)) &= u(x+1)v(x+1) - u(x)v(x) \\ &= u(x+1)v(x+1) - u(x)v(x+1) + u(x)v(x+1) - u(x)v(x) \\ &= \boldsymbol{\color{blue}v(x+1)(u(x+1) - u(x)) + u(x)(v(x+1) - v(x))\color{#3d4144}} \\ &= v(x+1)\Delta u(x) + u(x)\Delta v(x) \\ &= u(x)\Delta v(x) + v(x+1)\Delta u(x) \end{align}\]Last two lines just swapped to end up where the book does. This seemed kind of magical to me but, I suppose, it works due to the commutative and associative laws of addition/multiplication. I even tried it out with actual numbers and it works :D
My next moment of confusion came when deriving the actual summation by parts. We start with the compact rule for difference of a product using the $E$ “nuisance” (quoting the book here :) ):
\[\Delta(uv) = u\Delta v + Ev\Delta u\]Then they say:
\[\sum u\Delta v = uv - \sum Ev\Delta u\]Taking the indefinite sum on both sites of this equation, and rearranging its terms, yields the advertised rule for summation by parts:
I get the rearrangement of terms but how $\Delta(uv)$ becomes $uv$ was a bit of a mystery. After the requisite amount of time overthinking it, it is just literally following through the steps of turning $\Delta(uv)$ into $-\Delta(uv)$ when re-arranging; then shaking things out. I find it useful to expand everything and go from there:
\[\begin{align} -\Delta(uv) &= -\Delta(u(x)v(x)) \\ &= -(u(x+1)v(x+1) - u(x)v(x)) \\ &= -u(x+1)v(x+1) + u(x)v(x) \\ &= -u(x)v(x) \\ &= -uv \\ \end{align}\]Then, in 2.56 in the book, the rearrangment part makes it positive:
\[\begin{align} \Delta(uv) &= u\Delta v + Ev\Delta u \\ -u\Delta v &= -\Delta(uv) + Ev\Delta u \\ u\Delta v &= \Delta(uv) - Ev\Delta u \end{align}\]Finally they take the indefinite sum on both sides giving us the summation by parts rule
\[\sum u\Delta v = \Delta(uv) - \sum Ev\Delta u\]]]>