<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bhupesh Varshney 👾</title>
    <description>The latest articles on Forem by Bhupesh Varshney 👾 (@bhupesh).</description>
    <link>https://forem.com/bhupesh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F119873%2F5e10019f-97c6-4a67-9887-38d45b09bcf0.jpeg</url>
      <title>Forem: Bhupesh Varshney 👾</title>
      <link>https://forem.com/bhupesh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bhupesh"/>
    <language>en</language>
    <item>
      <title>How I reduced the size of my very first published docker image by 40% - A lesson in dockerizing shell scripts</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sat, 03 Feb 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/bhupesh/how-i-reduced-the-size-of-my-very-first-published-docker-image-by-40-a-lesson-in-dockerizing-shell-scripts-2fda</link>
      <guid>https://forem.com/bhupesh/how-i-reduced-the-size-of-my-very-first-published-docker-image-by-40-a-lesson-in-dockerizing-shell-scripts-2fda</guid>
      <description>&lt;p&gt;I interact with Dockerfiles every day at work, have written a few myself, built containers, and all that. But never published one on the docker hub registry. I wanted to make &lt;a href="https://github.com/Bhupesh-V/ugit" rel="noopener noreferrer"&gt;&lt;strong&gt;ugit&lt;/strong&gt; - a tool to undo git commands&lt;/a&gt; (written as a shell script) available to folks who don’t like installing random shell scripts from the internet.&lt;/p&gt;

&lt;p&gt;Yeah, I know, I know. REWRITE IT IN GO/RUST/MAGICLANG. The script is now more than 500+ lines of bash. I am not rewriting it in any other language unless someone holds a gun to my head (or maybe ends up sponsoring??). Moreover, ugit is close to being feature complete (only a few commands left to undo, which are not that commonly used).&lt;/p&gt;

&lt;p&gt;Anyway, the rest of the article is about how I went about writing the official Dockerfile for &lt;a href="https://github.com/Bhupesh-V/ugit/blob/master/ugit" rel="noopener noreferrer"&gt;&lt;strong&gt;ugit (a shell script)&lt;/strong&gt;&lt;/a&gt; and reduced the image size by almost &lt;strong&gt;40%&lt;/strong&gt; (&lt;em&gt;going from 31.4 MB to 17.6 MB&lt;/em&gt;) by performing step-by-step guided optimization attempts. I hope this motivates other shell enthusiasts to also publish their scripts as docker images!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PS: I am not a DevOps or Docker expert, so if you are and see something wrong or something that could be done better, please let me know in the comments below or reachout &lt;a href="https://bhupesh.me/about/#-connect" rel="noopener noreferrer"&gt;somewhere&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The very first &lt;code&gt;Dockerfile&lt;/code&gt; ~attempt~&lt;/li&gt;
&lt;li&gt;Path to optimization - reducing image size by 40%&lt;/li&gt;
&lt;li&gt;
2nd attempt - &lt;code&gt;alpine&lt;/code&gt; on &lt;code&gt;alpine&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Looks like &lt;code&gt;xargs&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; came free?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

3rd attempt - Using &lt;code&gt;scratch&lt;/code&gt; at 2nd stage

&lt;ul&gt;
&lt;li&gt;Identifying transitive dependencies&lt;/li&gt;
&lt;li&gt;Primer on shared libraries&lt;/li&gt;
&lt;li&gt;Shebangs &lt;code&gt;#!&lt;/code&gt; are useless&lt;/li&gt;
&lt;li&gt;A look at &lt;code&gt;terminfo&lt;/code&gt; db&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Everything needed to run &lt;code&gt;ugit&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;

Could we reduce the size further?

&lt;ul&gt;
&lt;li&gt;Reason 1: Pin minimum version of &lt;code&gt;fzf&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Reason 2: Use the latest bash features?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;You didn’t try &lt;code&gt;docker-slim&lt;/code&gt;?&lt;/li&gt;

&lt;li&gt;You didn’t try &lt;code&gt;docker-squash&lt;/code&gt;?&lt;/li&gt;

&lt;li&gt;Learnings&lt;/li&gt;

&lt;li&gt;Acknowledgements&lt;/li&gt;

&lt;li&gt;Resources&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The very first &lt;code&gt;Dockerfile&lt;/code&gt; ~attempt~
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use an official Alpine runtime as a parent image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.18&lt;/span&gt;

&lt;span class="c"&gt;# Set the working directory in the container to /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the current directory contents into the container at /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    gawk &lt;span class="se"&gt;\
&lt;/span&gt;    findutils &lt;span class="se"&gt;\
&lt;/span&gt;    coreutils &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    ncurses &lt;span class="se"&gt;\
&lt;/span&gt;    fzf

&lt;span class="c"&gt;# Set permissions and move the script to path&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ugit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;ugit /usr/local/bin/

&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looks pretty simple, right? It is. You could copy-paste this Dockerfile and build the image yourself assuming you have ugit cloned in the current directory,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t ugit .
docker run --rm -it -v $(pwd):/app ugit

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This should successfully run ugit inside the container.&lt;/p&gt;



&lt;p&gt;&lt;code&gt;ugit&lt;/code&gt; requires binaries like &lt;code&gt;bash (&amp;gt;=4.0)&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;xargs&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;fzf&lt;/code&gt;, &lt;code&gt;tput&lt;/code&gt;, &lt;code&gt;cut&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;nl&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We had to install &lt;a href="https://www.gnu.org/software/findutils/" rel="noopener noreferrer"&gt;findutils&lt;/a&gt; because it ships with &lt;code&gt;xargs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We had to install &lt;a href="https://wiki.debian.org/coreutils" rel="noopener noreferrer"&gt;coreutils&lt;/a&gt; because it ships with &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;cut&lt;/code&gt; and &lt;code&gt;nl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ncurses&lt;/code&gt; is required by &lt;code&gt;tput&lt;/code&gt; (which is used to get the terminal info).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all we need to run ugit on a UNIX-based machine or well, a container. The image size sits at &lt;code&gt;31.4 MB&lt;/code&gt; at this point. Not bad for a first attempt. Let’s see how we can reduce it further.&lt;/p&gt;
&lt;h2&gt;
  
  
  Path to optimization - reducing image size by 40%
&lt;/h2&gt;

&lt;p&gt;During our (desperate) micro-optimization attempts in the upcoming sections, we will be covering the following high-level goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use multi-stage builds to reduce image size.&lt;/li&gt;
&lt;li&gt;Get rid of binaries like &lt;code&gt;sleep&lt;/code&gt;, &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;du&lt;/code&gt; etc. Anything that is not required by &lt;code&gt;ugit&lt;/code&gt; to run.&lt;/li&gt;
&lt;li&gt;Get rid of unnecessary dependencies that these binaries bring in.&lt;/li&gt;
&lt;li&gt;Get a minimum version of all dependencies that are required by &lt;code&gt;ugit&lt;/code&gt; to run.&lt;/li&gt;
&lt;li&gt;Load up only the required binaries and their dependencies in the final image.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2nd attempt - &lt;code&gt;alpine&lt;/code&gt; on &lt;code&gt;alpine&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I now choose to create a multi-stage build. The 2nd stage will be used to copy only the required binaries and their dependencies. I again choose to use &lt;code&gt;alpine&lt;/code&gt; as the base image for this stage.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# First stage: Install packages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    gawk &lt;span class="se"&gt;\
&lt;/span&gt;    findutils &lt;span class="se"&gt;\
&lt;/span&gt;    coreutils &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    ncurses &lt;span class="se"&gt;\
&lt;/span&gt;    fzf

&lt;span class="c"&gt;# Copy only the ugit script into the container at /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ugit .&lt;/span&gt;

&lt;span class="c"&gt;# Set permissions and move the script to path&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ugit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;ugit /usr/local/bin/

&lt;span class="c"&gt;# Second stage: Copy only necessary binaries and their dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/ugit /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/git /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/fzf /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tput /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/cut /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tr /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/nl /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/gawk /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/xargs /usr/bin&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/env /bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /bin/bash /bin/&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Just by a straightforward multi-stage build, we were able to reduce the image size to an impressive &lt;code&gt;20.6&lt;/code&gt; MB. Now the image builds successfully, but it won’t run ugit yet.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error loading shared library libreadline.so.8: No such file or directory (needed by /bin/bash)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Turns out, we are missing transitive dependencies. More about this on our &lt;a href="https://dev.to/publishing-my-first-ever-dockerfile-optimization-ugit/#3rd-attempt---using-scratch-at-2nd-stage"&gt;3rd attempt&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Looks like &lt;code&gt;xargs&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; came free?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb64ppplcqp2uwtshf337.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb64ppplcqp2uwtshf337.gif" width="478" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turns out, that both &lt;code&gt;xargs&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; are present by default on the Alpine image. You can verify this by running the following commands:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it alpine /bin/sh -c "awk --help"
docker run -it alpine /bin/sh -c "xargs --help"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Rookie mistake. Let’s scratch &lt;code&gt;gawk&lt;/code&gt; and &lt;code&gt;findutils&lt;/code&gt; from our &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# First stage: Install packages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    coreutils &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    ncurses &lt;span class="se"&gt;\
&lt;/span&gt;    fzf

&lt;span class="c"&gt;# Copy only the ugit script into the container at /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ugit .&lt;/span&gt;

&lt;span class="c"&gt;# Set permissions and move the script to path&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ugit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;ugit /usr/local/bin/

&lt;span class="c"&gt;# Second stage: Copy only necessary binaries and their dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.18&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/ugit /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/git /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/fzf /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tput /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/cut /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tr /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/nl /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/env /bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /bin/bash /bin/&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The image size is now down to &lt;code&gt;20 MB&lt;/code&gt;. We are getting there. ugit still won’t run, though.&lt;/p&gt;
&lt;h2&gt;
  
  
  3rd attempt - Using &lt;a href="https://hub.docker.com/_/scratch/" rel="noopener noreferrer"&gt;&lt;code&gt;scratch&lt;/code&gt;&lt;/a&gt; at 2nd stage
&lt;/h2&gt;

&lt;p&gt;This is the part, that most folks don’t attempt. It’s a bit scary and requires a huge commitment. I was a bit scared to go this route as well because I knew everything could be &lt;del&gt;SCRATCHED&lt;/del&gt; 🙃.&lt;/p&gt;

&lt;p&gt;A SCRATCH docker image is just an empty file system. It doesn’t have anything at all. To try out a SCRATCH image, you can refer to &lt;a href="https://hub.docker.com/_/scratch" rel="noopener noreferrer"&gt;docker hub’s README on it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only thing you need to know is that everything has to be put together by us. Let’s keep our hands on our hearts and replace &lt;code&gt;alpine&lt;/code&gt; with &lt;code&gt;scratch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# First stage: Install packages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    coreutils &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    ncurses &lt;span class="se"&gt;\
&lt;/span&gt;    fzf

&lt;span class="c"&gt;# Copy only the ugit script into the container at /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ugit .&lt;/span&gt;

&lt;span class="c"&gt;# Set permissions and move the script to path&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ugit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;ugit /usr/local/bin/

&lt;span class="c"&gt;# Second stage: Copy only necessary binaries and their dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/ugit /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/git /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/fzf /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tput /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/cut /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/tr /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/nl /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/bin/env /bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /bin/bash /bin/&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Doing this reduced the size of our image to &lt;code&gt;12.4MB&lt;/code&gt;, a 60% reduction?? Did we just rickroll ourselves? Let’s try to run ugit.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker run --rm -it -v $(pwd):/app ugit-a3
exec /usr/bin/ugit: no such file or directory

$ docker run --rm -it --entrypoint /bin/bash ugit-a4
exec /bin/bash: no such file or directory

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy9zd5hucy0p8ovwseaz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy9zd5hucy0p8ovwseaz.gif" alt="resitas laughing" width="498" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turns out, we broke the bash binary by not shipping its dependencies. Let’s see what we can do about it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Identifying transitive dependencies
&lt;/h3&gt;

&lt;p&gt;Okay, time to talk about transitive dependencies. Our script relies on binaries like &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;tput&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;; now some of these utils may have their dependencies.&lt;/p&gt;

&lt;p&gt;We technically call these dependencies, shared libraries. Shared libraries are &lt;code&gt;.so&lt;/code&gt; (or in Windows &lt;code&gt;.dll&lt;/code&gt;, or in OS X &lt;code&gt;.dylib&lt;/code&gt;) files. &lt;a href="https://man7.org/linux/man-pages/man1/ldd.1.html" rel="noopener noreferrer"&gt;&lt;strong&gt;ldd&lt;/strong&gt;&lt;/a&gt; is a great tool to identify these dependencies. It lists all the libraries needed by a binary to execute. For example, if we run &lt;code&gt;ldd /bin/bash&lt;/code&gt; on a fresh Alpine container, we get the following output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ # ldd /bin/ls
    /lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
    libc.musl-aarch64.so.1 =&amp;gt; /lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
/ # ldd /bin/bash
    /lib/ld-musl-aarch64.so.1 (0xffffb905a000)
    libreadline.so.8 =&amp;gt; /usr/lib/libreadline.so.8 (0xffffb8f06000)
    libc.musl-aarch64.so.1 =&amp;gt; /lib/ld-musl-aarch64.so.1 (0xffffb905a000)
    libncursesw.so.6 =&amp;gt; /usr/lib/libncursesw.so.6 (0xffffb8e95000)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Primer on shared libraries
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Each lib has a &lt;em&gt;soname&lt;/em&gt;, which is the name of the library file without the version number. They start with the prefix &lt;code&gt;lib&lt;/code&gt; and end with &lt;code&gt;.so&lt;/code&gt;. For example, &lt;code&gt;libpcre2-8.so.0&lt;/code&gt; has a soname &lt;code&gt;libpcre2-8.so&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A fully qualified lib includes the directory where it is located. For example, &lt;code&gt;/usr/lib/libpcre2-8.so.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each lib has a real name, which is the name of the library file with the version number. For example, &lt;code&gt;libpcre2-8.so.0.3.6&lt;/code&gt; could be a real name.&lt;/li&gt;
&lt;li&gt;The hexadecimal is the base memory address where this library is loaded in memory. Not useful for us. We are only interested in the library names. Let’s do it for all the binaries we need to run ugit.&lt;/li&gt;
&lt;li&gt;The path to the right-hand side of &lt;code&gt;=&amp;gt;&lt;/code&gt; symbol indicates the real path to that shared library.&lt;/li&gt;
&lt;li&gt;The lib name starting with &lt;code&gt;libc&lt;/code&gt;is the C library for that architecture. Remember our “exec /bin/bash: no such file or directory” error? This is the reason we got it. We didn’t ship the C library for our architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is an excerpt from &lt;a href="https://twitter.com/dcreager/" rel="noopener noreferrer"&gt;Douglas Creager’s&lt;/a&gt; article on &lt;a href="https://dcreager.net/2017/10/shared-library-versions/" rel="noopener noreferrer"&gt;Shared library versions&lt;/a&gt; which sums up shared libraries, please read the full article if you want to learn more about shared libraries.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With a shared library, you compile the library once and install it into a shared location in the filesystem (typically &lt;code&gt;/usr/lib&lt;/code&gt; on Linux systems). Any project that depends on that shared library can use that shared, already compiled representation as-is.&lt;br&gt;&lt;br&gt;
Most Linux distributions further reduce compile times by distributing &lt;strong&gt;binary packages&lt;/strong&gt; of popular libraries, where the distribution’s packaging system has compiled the code for you. By installing the package, you download a (hopefully signed) copy of the compiled library, and place it into the shared location, all without ever having to invoke the compiler (or any other part of the build chain that produced the library).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We use a similar approach to identify all the unique dependencies for all the binaries we need to run ugit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ We did this by running a command like &lt;code&gt;for cmd in /bin/*; do echo $cmd; ldd $cmd; done&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy lib files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libpcre* /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libreadline* /usr/lib/&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libacl.so.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libattr.so.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libc.musl-* /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/ld-musl-* /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libutmps.so.0.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libskarnet.so.2.13 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libz.so.1 /lib/&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice, that we are copying &lt;code&gt;libc.musl-*&lt;/code&gt; and &lt;code&gt;ld-musl-*&lt;/code&gt; from the builder image. This is because the build for these libs depends on the architecture of the host machine.&lt;/p&gt;
&lt;h3&gt;
  
  
  Shebangs &lt;code&gt;#!&lt;/code&gt; are useless
&lt;/h3&gt;

&lt;p&gt;If you look at the very &lt;a href="https://github.com/Bhupesh-V/ugit/blob/master/ugit#L1" rel="noopener noreferrer"&gt;first line of ugit&lt;/a&gt;, you’ll see a shebang &lt;code&gt;#!/usr/bin/env bash&lt;/code&gt;. The use of &lt;code&gt;env&lt;/code&gt; is considered a good practice when writing shell scripts, used to tell the OS which shell interpreter to use to run the script, this is ideal in an everyday dev machine. Linux (and older versions of macOS) get shipped with &lt;code&gt;sh&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, and on top of it, folks install &lt;code&gt;zsh&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;But since using shebangs is optional, and we already copy the &lt;code&gt;bash&lt;/code&gt; binary, we just need to invoke our script using it. This saves us a couple of bytes in the image size as well. Close to 1.9 MB to be precise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhuyyx06e3w4y8jheb94.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhuyyx06e3w4y8jheb94.gif" alt="get rid of shebang" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "/bin/ugit"]&lt;/span&gt;
&lt;span class="c"&gt;# or &lt;/span&gt;
&lt;span class="c"&gt;# ENTRYPOINT ["/bin/bash", "/bin/ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  A look at &lt;code&gt;terminfo&lt;/code&gt; db
&lt;/h3&gt;

&lt;p&gt;ugit has colors, thanks to &lt;code&gt;tput&lt;/code&gt;. We load up a bare-bones Alpine image with &lt;code&gt;bash&lt;/code&gt; and head over to the &lt;code&gt;/etc/terminfo&lt;/code&gt; directory. This directory contains the terminal info database.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;37a1a77f70ed:/app# cd /etc/terminfo/
37a1a77f70ed:/etc/terminfo# ls
a d g k l p r s t v x

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each of these letter-based “directories” represents different &lt;code&gt;$TERM&lt;/code&gt; types. For example, &lt;code&gt;xterm&lt;/code&gt; is a terminal type. If you run &lt;code&gt;tput -T xterm colors&lt;/code&gt; on your local machine, you’ll get the number of colors your terminal supports. For &lt;code&gt;xterm&lt;/code&gt; it should be &lt;code&gt;8&lt;/code&gt;, and in the case of &lt;code&gt;xterm-256color&lt;/code&gt; it should be &lt;code&gt;256&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now here’s our chance, to only support 1 terminal type amongst the 40+ that are present in the &lt;code&gt;terminfo&lt;/code&gt; db. We can get rid of the rest of the terminal types. This saves us another 97Kb, very little but needed to clear up the clutter.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy terminfo database for only xterm-256color&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/&lt;/span&gt;

&lt;span class="c"&gt;# Gib me all the colors&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TERM=xterm-256color&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Everything needed to run &lt;code&gt;ugit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum5ukbpcbria5tsioyei.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum5ukbpcbria5tsioyei.gif" alt="everything-everywhere" width="480" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final Docker image sits at 17.6 MB with no security vulnerabilities (as reported by &lt;a href="https://docs.docker.com/scout/" rel="noopener noreferrer"&gt;docker scout&lt;/a&gt;, at the time of writing this article). We have successfully reduced the image size by 40% compare to our &lt;a href="https://dev.to/publishing-my-first-ever-dockerfile-optimization-ugit/#the-very-first-dockerfile-attempt"&gt;first attempt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the final Dockerfile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ugit-ops&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    coreutils &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    ncurses &lt;span class="se"&gt;\
&lt;/span&gt;    curl

&lt;span class="c"&gt;# Download fzf binary from GitHub, pin to 0.46.0, ugit requires minimum 0.21.0&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; fzf.tar.gz https://github.com/junegunn/fzf/releases/download/0.46.0/fzf-0.46.0-linux_amd64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; fzf.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mv &lt;/span&gt;fzf /usr/bin/

&lt;span class="c"&gt;# Copy only the ugit script into the container at /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ugit .&lt;/span&gt;

&lt;span class="c"&gt;# Set permissions and move the script to path&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ugit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;ugit /usr/bin/

&lt;span class="c"&gt;# Second stage: Copy only necessary binaries and their dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/ugit /bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/git /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/fzf /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/tput /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/nl /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/awk /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/xargs /usr/bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/cut /usr/bin/cut&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/bin/tr /usr/bin/tr&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /bin/bash /bin/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /bin/sh /bin/&lt;/span&gt;

&lt;span class="c"&gt;# copy lib files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libpcre* /usr/lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /usr/lib/libreadline* /usr/lib/&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libacl.so.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libattr.so.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libc.musl-* /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/ld-musl-* /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libutmps.so.0.1 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libskarnet.so.2.13 /lib/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /lib/libz.so.1 /lib/&lt;/span&gt;

&lt;span class="c"&gt;# copy terminfo database&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/&lt;/span&gt;

&lt;span class="c"&gt;# Gib me all the colors&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TERM=xterm-256color&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="c"&gt;# Run ugit when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash", "/bin/ugit"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I decided to pin the version of &lt;code&gt;fzf&lt;/code&gt; to &lt;code&gt;0.46.0&lt;/code&gt; (the latest at the time of writing this article) because ugit requires a minimum &lt;code&gt;0.21.0&lt;/code&gt; to run, and I figured what the heck, let’s pin it to the latest version.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;code&gt;docker run --rm -it -v $(pwd):/app ugit&lt;/code&gt; will run the ugit container. Make sure your current directory is a git repo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is how the final file system tree looks like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is everything to make our shell app work. No more, no less. Time for a beer? 🍺&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PS: The final docker image came down to 16.2 MB. You can find the updated Dockerfile &lt;a href="https://github.com/Bhupesh-V/ugit/blob/master/Dockerfile" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For the sake of this article, I kept the image size at 17.6 MB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Could we reduce the size further?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zz6kz6es9owqnv4mwth.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zz6kz6es9owqnv4mwth.gif" width="480" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, but there are 2 reasons why I didn’t go any further:&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 1: Pin minimum version of &lt;code&gt;fzf&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;At the time of writing ugit, the script relied on &lt;a href="https://github.com/junegunn/fzf/releases/tag/0.20.0" rel="noopener noreferrer"&gt;&lt;code&gt;fzf&lt;/code&gt; minimum version &lt;code&gt;0.20.0&lt;/code&gt;&lt;/a&gt;, it’s granted that the latest version is going to be larger than the minimum required version. So we should pin the old version, right? No. because then it introduces security vulnerabilities with fzf’s dependencies, i.e., Golang. As reported by &lt;code&gt;docker scout quickview&lt;/code&gt;, the older version of Golang has a total of 66 security issues. Maybe they affect the image, maybe they don’t. But I am not taking that risk, I want to keep the image as clean as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vuniqsd50u56yr4ocnb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vuniqsd50u56yr4ocnb.png" alt="security issues in golang" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ In the Alpine ecosystem, it is generally not advised to pin minimum versions of packages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Reason 2: Use the latest bash features?
&lt;/h3&gt;

&lt;p&gt;At the time of writing ugit, I relied on &lt;a href="https://ftp.gnu.org/gnu/bash/" rel="noopener noreferrer"&gt;&lt;code&gt;bash&lt;/code&gt; version &lt;code&gt;4.0&lt;/code&gt;&lt;/a&gt;. Both tr and cut could be replaced, if I shifted to a newer version of bash. i.e, &lt;em&gt;5.0&lt;/em&gt;. And that my friends is a breaking change. Getting rid of and would have saved me a couple of bytes, but I didn’t want to break the script for folks who are still on bash 4.0. It doesn’t matter if I am the only one left, my machine still has bash.&lt;/p&gt;

&lt;h2&gt;
  
  
  You didn’t try &lt;code&gt;docker-slim&lt;/code&gt;?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I did, it did slim down my image, but it also broke the script with missing dependencies. Slim is great, the reader should check it out. Unfortunately, I couldn’t get it to work for ugit in my limited time.&lt;/li&gt;
&lt;li&gt;Moreover, I wanted to learn how to do it with my own hands, rather than rely on a tool to do it for me.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  You didn’t try &lt;code&gt;docker-squash&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;I did, and the size optimization was nearly negligible. Here’s a log of the squash process which I ran on a Linux (AMD) machine (ignore the size of the image, since we are on different architecture, the image size is different):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux is awesome. Everything, every design decision, every tool inspiration that came out of it inherits the same awesomeness.&lt;/li&gt;
&lt;li&gt;Never shy away from going into deep 🐇 rabbit holes of micro-optimizations. You learn a lot. There’s always something new to learn.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Big thanks to the authors of &lt;code&gt;ldd&lt;/code&gt;, and everyone in the &lt;code&gt;docker&lt;/code&gt; &amp;amp; &lt;code&gt;alpine&lt;/code&gt; linux community.&lt;/li&gt;
&lt;li&gt;Thanks to folks on the &lt;strong&gt;developersIndia&lt;/strong&gt; discord for helping out with advice &amp;amp; suggestions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html" rel="noopener noreferrer"&gt;TLDP.org - Shared Libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/" rel="noopener noreferrer"&gt;Dockerfile best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/storage/storagedriver/" rel="noopener noreferrer"&gt;About docker storage drivers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you guys got something interesting to read today. Until next time, happy hacking &amp;amp; before you go please give &lt;a href="https://github.com/Bhupesh-V/ugit" rel="noopener noreferrer"&gt;ugit&lt;/a&gt; a star &amp;amp; a &lt;a href="https://hub.docker.com/r/bhupeshimself/ugit" rel="noopener noreferrer"&gt;docker pull&lt;/a&gt;? 🧡&lt;/p&gt;

</description>
      <category>shell</category>
      <category>linux</category>
      <category>git</category>
      <category>docker</category>
    </item>
    <item>
      <title>Using multiple git user configs with credentials store</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sat, 18 Feb 2023 15:05:41 +0000</pubDate>
      <link>https://forem.com/bhupesh/using-multiple-git-user-configs-with-credentials-store-4p5</link>
      <guid>https://forem.com/bhupesh/using-multiple-git-user-configs-with-credentials-store-4p5</guid>
      <description>&lt;p&gt;So, you have decided to have 2 separate accounts for work and personal.&lt;br&gt;
Here are steps to follow to use both git configurations simultaneously with the same &lt;code&gt;.git-credentials&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Separate Work/Personal directories
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;work&lt;/code&gt; and a &lt;code&gt;personsal&lt;/code&gt; directory in your Documents folder. Use these directories to separate your git directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  Documents
  ├── work
  │   ├── ...
  │   └── ...
  └── personal
      ├── ...
      └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Separate git config for each account
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;.gitconfig-personal&lt;/code&gt; in the &lt;code&gt;personal&lt;/code&gt; dir.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [credential]
    helper = store
  [user]
    name = Personal_Username
    email = personal_email@domain.com
  [credential "https://github.com"]
    username = Personal_Username
    helper = store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;.gitconfig-work&lt;/code&gt; in the &lt;code&gt;work&lt;/code&gt; dir.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [credential]
    helper = store
  [user]
    name = Work_Username
    email = work_email@domain.com
  [credential "https://github.com"]
    username = Work_Username
    helper = store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Update global &lt;code&gt;.gitconfig&lt;/code&gt; to switch the profile based on directory
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update your global config using the following command.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git config &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--edit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add the following config.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="o"&gt;[&lt;/span&gt;includeIf &lt;span class="s2"&gt;"gitdir:~/Documents/work/"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
      path &lt;span class="o"&gt;=&lt;/span&gt; ~/Documents/work/.gitconfig-work
  &lt;span class="o"&gt;[&lt;/span&gt;includeIf &lt;span class="s2"&gt;"gitdir:~/Documents/personal/"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
      path &lt;span class="o"&gt;=&lt;/span&gt; ~/Documents/personal/.gitconfig-personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Register Access Tokens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check your &lt;code&gt;.git-credentials&lt;/code&gt; file &amp;amp; add &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;GitHub access tokens&lt;/a&gt; for each account.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  https://Personal_Username:PERSONAL_TOKEN@github.com
  https://Work_Username:WORK_TOKEN@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, the next time you pull/push/clone a private repo, git will automatically choose the correct token for each config.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>How to stop having disappointing moments in life</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sun, 28 Aug 2022 05:16:00 +0000</pubDate>
      <link>https://forem.com/bhupesh/how-to-stop-having-disappointing-moments-in-life-9ji</link>
      <guid>https://forem.com/bhupesh/how-to-stop-having-disappointing-moments-in-life-9ji</guid>
      <description>&lt;p&gt;How many time have you been let down by your expectations?&lt;/p&gt;

&lt;p&gt;Seriously, “expecting” anything from anyone is the root cause of disappointment in your life. You start leveling up your hopes in dream of “getting X” but in reality you don’t. Its sad but that’s how life works, its yin-yang remember?&lt;/p&gt;

&lt;p&gt;Consider your average common scenario that can occur in everyone’s life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Didn’t get the reply from crush?&lt;/li&gt;
&lt;li&gt;Didn’t get that job?&lt;/li&gt;
&lt;li&gt;Didn’t get that promotion?&lt;/li&gt;
&lt;li&gt;Didn’t get that scholarship?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… the list goes on.&lt;/p&gt;

&lt;p&gt;Consider a real-life absurd example which recently happened with me, I am a big MCU (Marvel Cenamatic Universe) buff, so I go to every Marvel movie that comes up next.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lot of folks are excited about the release of &lt;em&gt;Spiderman No Way Home&lt;/em&gt;, me on another hand has no expectations from this movie (probably not a big fan). I went to go this movie, and there you go in the first 20 minutes or so Daredevil pops up (I love this character, I would recommend watching the Daredevil TV series on Hotstar to understand why this was a big moment in the movie), nobody knew about this and honestly this moment made the movie 10x for me. No disappointment whatsoever.&lt;/li&gt;
&lt;li&gt;Now consider the release of &lt;em&gt;Doctor Strange in Multiverse of Madness&lt;/em&gt;, my hopes for this were high because we knew so much could happen in this movie but Marvel decided to go lightweight this time (I mean yes, it was a good storyline) but I was a little “disappointed” thinking a “lot of things” could happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know this is very circumstantial &amp;amp; personal, but I encourage you to have a look at moments from your life on how many times have your felt something similar.&lt;/p&gt;

&lt;p&gt;Getting disappointed with something can lead you to demotivation and self-doubt. Now this doesn’t mean to not do anything when expectations are not met. Let’s say you ordered &lt;em&gt;chicken biryani&lt;/em&gt; using a food delivery app and if on arrival you don’t see any chicken in biryani then definitely raise a complaint to the app instead of letting it go.&lt;/p&gt;

&lt;p&gt;My point is to do things that are in your control. Don’t overload it, Don’t overthink it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do then?
&lt;/h2&gt;

&lt;p&gt;Now I am not a life-coach but I believe simple changes in your thinking capacity can eliminate disappointment from your life.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just give your best effort on things you can control. Whatever the results may be, positive or negative, accept them and move on. And ofc make a mental note on what things to improve on future.&lt;/li&gt;
&lt;li&gt;Another thing that I like to practice nowadays is whenever I start raising my hope levels, I try to ground my thoughts back to reality, Just realising you are thinking to much helps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try to adapt a similar practice in your life as well, Say avoid overthinking things like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Oh God, If I had this much money I would buy …&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;If I get my dream job, I would do this and this …&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this helps the reader to atleast ponder over eliminating disappointing thoughts, until next time 👋&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>productivity</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Automatically remind contributors to update their pull requests</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sun, 28 Aug 2022 05:09:00 +0000</pubDate>
      <link>https://forem.com/bhupesh/automatically-remind-contributors-to-update-their-pull-requests-2ame</link>
      <guid>https://forem.com/bhupesh/automatically-remind-contributors-to-update-their-pull-requests-2ame</guid>
      <description>&lt;p&gt;So from my short professional experience, I have realised that if you are working with a small team of individuals a lot of folks end-up authoring multiple pull-requests across X different repositories in an organisation which sometimes leads to managing multiple feature PRs at a time.&lt;/p&gt;

&lt;p&gt;This can sometimes leads to author forgetting to update their branches and test their code with the most stable (or latest) version possible. Since a lifetime of a pull request can be anything from 1 day to 1 week, its important to keep your branches up to date. But we developers forget to do this timely.&lt;/p&gt;

&lt;p&gt;This is not a problem of just small teams, opensource projects (be it small or big) usually have this problem as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix?
&lt;/h2&gt;

&lt;p&gt;I went ahead and made a minimal github action to solve the above problem by reminding contributors to update their branches whenever base branch gets a new commit. We can do this by simply leaving a comment on pull requests (you can see a demo in the banner of this post)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Bhupesh-V/update-pr-reminder-action" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgh-card.dev%2Frepos%2FBhupesh-V%2Fupdate-pr-reminder-action.svg%3Ffullname%3D" alt="Bhupesh-V/update-pr-reminder-action - GitHub" width="442" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a simple workflow on how to setup this action&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR Update Reminder&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# make sure to set this as env&lt;/span&gt;
  &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt; 

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;remind_authors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update PR Reminder Test&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bhupesh-V/update-pr-reminder-action@main&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above workflow we are telling github to “watch” the &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;dev&lt;/code&gt; branches of our repository for any new pushes and run the corressponding action. So what’ happening behind the scenes at &lt;em&gt;update-pr-reminder-action&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;We use a simple shell script combined with &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;github’s CLI tool, &lt;code&gt;gh&lt;/code&gt;&lt;/a&gt; and some some shell wizadry ✨&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;all_open_prs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;list &lt;span class="nt"&gt;--base&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REF&lt;/span&gt;&lt;span class="p"&gt;#refs/*/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; author,number&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"PRs with base &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REF&lt;/span&gt;&lt;span class="p"&gt;#refs/*/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;prs_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq length&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"There are currently &lt;/span&gt;&lt;span class="nv"&gt;$prs_count&lt;/span&gt;&lt;span class="s2"&gt; open PRs"&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; c&amp;lt;&lt;span class="nv"&gt;$prs_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; c++ &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;pr_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.number&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.author.login | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Author for PR &lt;/span&gt;&lt;span class="nv"&gt;$pr_id&lt;/span&gt;&lt;span class="s2"&gt; is &lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    gh &lt;span class="nb"&gt;pr &lt;/span&gt;comment &lt;span class="nv"&gt;$pr_id&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Hey @&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="s2"&gt; 👋🏽 friendly reminder to update your PR/branch because there was a recent commit (&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;) to the base branch"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing we need to do is find the current branch, which is done by &lt;code&gt;${GITHUB_REF#refs/*/}&lt;/code&gt; where &lt;code&gt;GITHUB_REF&lt;/code&gt; is an &lt;a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables" rel="noopener noreferrer"&gt;environment variable&lt;/a&gt; which is set automatically in every github action&lt;/p&gt;

&lt;p&gt;Next we find all the open PRs open using the &lt;code&gt;gh&lt;/code&gt; tool&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;list &lt;span class="nt"&gt;--base&lt;/span&gt; main &lt;span class="nt"&gt;--json&lt;/span&gt; author,number

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;gh&lt;/code&gt; needs to authenticate before you can use it, we have already done this by setting the env &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;. &lt;a href="https://cli.github.com/manual/gh_help_environment" rel="noopener noreferrer"&gt;&lt;code&gt;gh&lt;/code&gt; uses this token&lt;/a&gt; to make any API requests&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This will return a json output of all the open PRs like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bhupesh-V"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"some-dev"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To parse json, we are using &lt;code&gt;jq&lt;/code&gt; here,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# get pull request id&lt;/span&gt;
&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.number&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a comment is easy,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;comment &lt;span class="nv"&gt;$pr_id&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Comment body"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see the comment body in our action, we are also referencing the latest commit to base branch using &lt;code&gt;git rev-parse HEAD&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey @$author 👋🏽 friendly reminder to update your PR/branch because there was a recent merge ($(git rev-parse HEAD)) to the base branch

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TODO
&lt;/h2&gt;

&lt;p&gt;To reduce spam in case a pull request takes longer to get merged, we can also check if the branch is up to date by check if the base branches latest &lt;code&gt;HEAD&lt;/code&gt; is present inside the authors PR&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Ideally this feature should exist alongside &lt;a href="https://docs.github.com/en/organizations/organizing-members-into-teams/managing-scheduled-reminders-for-your-team" rel="noopener noreferrer"&gt;scheduled reminders&lt;/a&gt; nonetheless I believe its a good enough solution for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;A bunch of resources that helped me write this workflow&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/using-github-cli-in-workflows" rel="noopener noreferrer"&gt;Using Github CLI in workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/using-github-cli-in-workflows" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt; cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>productivity</category>
      <category>bash</category>
      <category>programming</category>
    </item>
    <item>
      <title>What's the best risk you have taken?</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sun, 10 Jul 2022 08:59:04 +0000</pubDate>
      <link>https://forem.com/bhupesh/whats-the-best-risk-you-have-taken-28l2</link>
      <guid>https://forem.com/bhupesh/whats-the-best-risk-you-have-taken-28l2</guid>
      <description>&lt;p&gt;People talk about taking risks in life. Would love to hear what's a risk that turned to be good for you eventually?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>If I could change one thing about tech interviews</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Sat, 09 Jul 2022 08:32:10 +0000</pubDate>
      <link>https://forem.com/bhupesh/if-i-could-change-one-thing-about-tech-interviews-3464</link>
      <guid>https://forem.com/bhupesh/if-i-could-change-one-thing-about-tech-interviews-3464</guid>
      <description>&lt;p&gt;So I have been interviewing for a while for my next role in tech. I have given all type of interviews by now. Take home assignments, DSA, solution write-ups you name it. I am fine with all kinds of interviews, honestly at this moment I don’t even care about what style of interviews are good or bad.&lt;/p&gt;

&lt;p&gt;What I would like to see a change in our ecosystem is to &lt;strong&gt;GIVE FEEDBACK AFTER INTERVIEWING&lt;/strong&gt;. Yes I am screaming because I can’t even believe this simple and most minimal amount of decency is not followed anywhere, and nobody seems to give a shit about it?&lt;/p&gt;

&lt;p&gt;I say forget about giving feedback at application stage, but what about when a candidate sits in your 3-round and 1-hour long tech interviews &amp;amp; you don’t even bother to say why they were rejected? Is this what our industry have come down to?&lt;/p&gt;

&lt;p&gt;We talk about upskilling and continuous learning from candidate, but is your company upskilling &amp;amp; learning from your interview taking skills? Or is it always about filling the funnel &amp;amp; hiring the best-of-best?&lt;/p&gt;

&lt;h2&gt;
  
  
  One personal anecdote
&lt;/h2&gt;

&lt;p&gt;Even among all this non-sense &amp;amp; chaotic style of interviewing, I happen to have one of my best interviewing experience with &lt;a href="https://deepsource.io" rel="noopener noreferrer"&gt;deepsource&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;3 rounds of interview:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intro call about my work &amp;amp; what I do?&lt;/li&gt;
&lt;li&gt;Code Pairing session (sort of pair-programming) where we built &lt;a href="https://github.com/Bhupesh-V/ratelimiter-demo" rel="noopener noreferrer"&gt;something meaningful&lt;/a&gt; in 1.2 hrs.&lt;/li&gt;
&lt;li&gt;A traditional system design round.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even though I got rejected, but upon asking for feedback this is what I got in reply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3biw131deo31pt87p2s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3biw131deo31pt87p2s.jpg" alt="deepsource-interview-feedback" width="720" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I whole heartedly agree 💯 with both the points, My system-design skills have always been in a gray area and I try to improve them form time-to-time. Atleast now I have some concrete proof that I need to work in that area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some polite feedback for Senior Engineers/Managers/CTOs/Founders
&lt;/h2&gt;

&lt;p&gt;For folks who are in-charge of taking interviews. How do you communicate to the HR that this candidate is not good enough? You obviously have some good reasons right? You know them at the back of your head, what’s stopping you from writing one 2 line paragraph about it?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aren’t we told that a “good software engineer” has good communication skills apart from coding skills. If that’s the case take a step back and ponder how you folks communicate “during” and “after” the interview process is over. Can you try to improve it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you say you don’t have time to right down your thoughts because of large number of interviews, Or maybe back-to-back interviews or maybe tight deadlines etc. Here are some minimal tips you could follow&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Full disclosure: I am not an interviewer, never have I ever interviewed anyone but I believe this is how would I go about doing it)&lt;/em&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reserve some 5-10 minutes before the interview to go through candidate’s resume/portfolio. Get an understanding what do they write about themselves, read every resume as a short story, this would probably take 5 mins max.&lt;/li&gt;
&lt;li&gt;During the interview when they are busy solving problems (and there is a awkward silence), right your understanding of the candidate.&lt;/li&gt;
&lt;li&gt;After the interview is over, and you have made your mind not to move forward with them, Take 2 mins to write what caused you to make that decision and ask your HR to &lt;em&gt;“copy-paste”&lt;/em&gt; and send it to the candidate with the obvious &lt;em&gt;“rejection-template-copy”&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s all we ask. Literally how hard is it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Advice for candidates
&lt;/h2&gt;

&lt;p&gt;Just keep giving your best in all rounds. One thing I have learned recently is that even if everything goes great from your end, there is always a probability of you not getting selected. It’s not a fair game (it never will be). Just don’t give up and keep learning. Hope you get that job 👍&lt;/p&gt;

&lt;p&gt;Adios 👋&lt;/p&gt;

</description>
      <category>programming</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Questions to ask in an interview as an interviewee</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Mon, 20 Jun 2022 05:51:38 +0000</pubDate>
      <link>https://forem.com/bhupesh/questions-to-ask-in-an-interview-as-an-interviewee-o8j</link>
      <guid>https://forem.com/bhupesh/questions-to-ask-in-an-interview-as-an-interviewee-o8j</guid>
      <description>&lt;p&gt;Hey folks, If you are actively interviewing, here are a bunch of questions you can ask as an interviewee.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What’s the biggest priority for the team right now?&lt;/li&gt;
&lt;li&gt;Why is this role open?&lt;/li&gt;
&lt;li&gt;What’s the biggest challenge for someone stepping into this role?&lt;/li&gt;
&lt;li&gt;How does the org structure on the team work?&lt;/li&gt;
&lt;li&gt;What a typical day is like?&lt;/li&gt;
&lt;li&gt;Is the product live in production? If not, what's the schedule for developing it? (Particularly valid for early stage &amp;amp; stealth startups)&lt;/li&gt;
&lt;li&gt;How often are releases done?&lt;/li&gt;
&lt;li&gt;Do you have a favorite part of the job? Least favorite?&lt;/li&gt;
&lt;li&gt;Budget for conferences?&lt;/li&gt;
&lt;li&gt;Management style/structure? Frequent catch-ups aka one-on-ones? Something else?&lt;/li&gt;
&lt;li&gt;Learning opportunities?&lt;/li&gt;
&lt;li&gt;Ask about last employee who left, what was the reason?&lt;/li&gt;
&lt;li&gt;How managers/senior folks help coach employees through a weakness.&lt;/li&gt;
&lt;li&gt;How often feedback is given to you and collected from you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://daveceddia.com/interview-questions-to-ask-company/?utm_content=197408362&amp;amp;utm_medium=social&amp;amp;utm_source=twitter&amp;amp;hss_channel=tw-4083531" rel="noopener noreferrer"&gt;Interview Questions to Ask Your Interviewer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to share more questions below&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Writing like a pro with vale &amp; neovim</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Thu, 26 May 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/bhupesh/writing-like-a-pro-with-vale-neovim-3eij</link>
      <guid>https://forem.com/bhupesh/writing-like-a-pro-with-vale-neovim-3eij</guid>
      <description>&lt;p&gt;&lt;a href="https://vale.sh" rel="noopener noreferrer"&gt;Vale&lt;/a&gt; is a syntax-aware prose linter built for all you writers out there. With more than 100 releases so far vale is 5 year old project and is used by writing folks in companies like Google, Microsoft, IBM, RedHat to name a few. I have recently started to use vale in my everyday writing workflow and it has a significant impact on what words I choose to convey ideas. I mostly use neovim for writing, so we will be covering how to setup &lt;code&gt;vale&lt;/code&gt; and use it with neovim.&lt;/p&gt;

&lt;p&gt;Just like when writing software we use static analysis tools to find common problems, &lt;code&gt;vale&lt;/code&gt; aims to help writers configure what words/prose to choose while writing technical documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing &lt;code&gt;vale&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Download a latest version of vale from their &lt;a href="https://github.com/errata-ai/vale/releases" rel="noopener noreferrer"&gt;github releases page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing styles &amp;amp; the vale config
&lt;/h2&gt;

&lt;p&gt;Vale requires to have a &lt;code&gt;.vale.ini&lt;/code&gt; config file located either in your &lt;code&gt;$HOME&lt;/code&gt; directory or a relative project directory. Below is a sample configuration that I use personally&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# This goes in a file named either `.vale.ini` or `_vale.ini`.
&lt;/span&gt;&lt;span class="py"&gt;StylesPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;styles&lt;/span&gt;
&lt;span class="py"&gt;MinAlertLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;suggestion&lt;/span&gt;

&lt;span class="c"&gt;# External packages
&lt;/span&gt;&lt;span class="py"&gt;Packages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Google, Readability, alex, proselint, IBM&lt;/span&gt;
&lt;span class="c"&gt;# Only Markdown and .txt files; change to whatever you're using.
&lt;/span&gt;&lt;span class="nn"&gt;[*.{md,txt}]&lt;/span&gt;
&lt;span class="c"&gt;# List of styles to load.
&lt;/span&gt;&lt;span class="py"&gt;BasedOnStyles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;alex, proselint&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you create this file, run &lt;code&gt;vale sync&lt;/code&gt; to download/update any External packages you have mentioned in the config.&lt;/p&gt;

&lt;p&gt;A vale style is a repository of “rules” that define what words need to be reported by vale. The rules are defined in a &lt;code&gt;yml&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you prefer to customize the config yourself, I recommend using the &lt;a href="https://vale.sh/generator/" rel="noopener noreferrer"&gt;config generator&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The vale project has come up with &lt;a href="https://vale.sh/types/style/" rel="noopener noreferrer"&gt;7 style packages&lt;/a&gt;. Some of the popular styles are based on writing rules from organisations like Google &amp;amp; Microsoft, some of which are listed below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/style-guide/welcome/" rel="noopener noreferrer"&gt;Microsoft Writing Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/style/" rel="noopener noreferrer"&gt;Google Developer Documentation Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ibm.com/developerworks/library/styleguidelines/index.html" rel="noopener noreferrer"&gt;IBM’s Developer Editorial Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find more vale styles on github by &lt;a href="https://github.com/topics/vale-style" rel="noopener noreferrer"&gt;vale-style topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Choosing what styles suits best for your writing needs depends on your writing style, If you are working with a team of writers in a organisation I would suggest using styles from Google &amp;amp; Microsoft which are more strict but good for teams. If you have a personal blog or you write solo, use styles like &lt;code&gt;alex&lt;/code&gt; or &lt;code&gt;proselint&lt;/code&gt; whose rules are more lenient, I recommend give all the rules a try by switching the &lt;code&gt;BasedOnStyles&lt;/code&gt; property in vale config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Neovim
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/jose-elias-alvarez/null-ls.nvim" rel="noopener noreferrer"&gt;null-ls.nvim&lt;/a&gt; neovim plugin let’s you use vale as a prose linter.&lt;/p&gt;

&lt;p&gt;Install null-ls.nvim using &lt;code&gt;Plug&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;Plug &lt;span class="s1"&gt;'jose-elias-alvarez/null-ls.nvim'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Require the lua plugin,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"null-ls"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"null-ls"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;builtins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a native &lt;code&gt;vim&lt;/code&gt; config use lua HERE doc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;lua&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; EOF
require&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"null-ls"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;setup&lt;span class="p"&gt;({&lt;/span&gt;
    sources &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        require&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"null-ls"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;builtins&lt;span class="p"&gt;.&lt;/span&gt;diagnostics&lt;span class="p"&gt;.&lt;/span&gt;vale&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
EOF

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Make sure you have a &lt;code&gt;.vale.ini&lt;/code&gt; or &lt;code&gt;_vale.ini&lt;/code&gt; somewhere in &lt;code&gt;$HOME&lt;/code&gt; or at the current working directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is how the in-editor suggestions look like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr9zhs8rk2rloi7qu759.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr9zhs8rk2rloi7qu759.png" alt="vale-with-null-ls" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, some of the suggestions are not being rendered by NeoVim correctly. To fix this, you can use the &lt;a href="https://github.com/folke/trouble.nvim" rel="noopener noreferrer"&gt;trouble.nvim&lt;/a&gt; plugin to get a nice VSCode like interface for diagnostics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6ox841k441cp0wew982.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6ox841k441cp0wew982.png" alt="vale-vim-demo" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it, enjoy writing!&lt;/p&gt;

</description>
      <category>vim</category>
      <category>neovim</category>
    </item>
    <item>
      <title>Find pull requests that modify a file path in the terminal</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Fri, 22 Apr 2022 06:39:15 +0000</pubDate>
      <link>https://forem.com/bhupesh/find-pull-requests-that-modify-a-file-path-in-the-terminal-3pd7</link>
      <guid>https://forem.com/bhupesh/find-pull-requests-that-modify-a-file-path-in-the-terminal-3pd7</guid>
      <description>&lt;p&gt;Ever wondered how others are changing the same file you are working on? It can give insights into what a particular piece of code will look like in the future. Certainly, it can also help OSS newcomers to identify how others are building a particular feature&lt;/p&gt;

&lt;p&gt;Well, today’s post is to solve just that. Let’s build a simple shell script for ourselves, shall we?&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start working on anything, make sure you have the following CLI tools installed&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;&lt;code&gt;gh&lt;/code&gt;- Github’s CLI tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf#installation" rel="noopener noreferrer"&gt;&lt;code&gt;fzf&lt;/code&gt; - A command-line fuzzy finder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stedolan/jq/releases" rel="noopener noreferrer"&gt;&lt;code&gt;jq&lt;/code&gt; - Command-line JSON processor&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Once you install &lt;code&gt;gh&lt;/code&gt;, make sure to authenticate yourself using &lt;code&gt;gh auth login&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Gimme pull requests
&lt;/h2&gt;

&lt;p&gt;Our first job is to ask the user to choose a local file path to look for. This can be done by parsing &lt;code&gt;git ls-files&lt;/code&gt;, for better interactivity we are going to use &lt;code&gt;fzf&lt;/code&gt; as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;git_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git ls-files | fzf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Choose File: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--height&lt;/span&gt; 40% &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Choose a file to find pull requests on github that modify it"&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that’s settled we need to find all the open pull requests, this is done by &lt;code&gt;gh pr list&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetch_prs&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;pr_fetch_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Hold tight while we look for PRs ✋👀 that modify &lt;/span&gt;&lt;span class="nv"&gt;$git_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;all_open_prs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;list &lt;span class="nt"&gt;--limit&lt;/span&gt; &lt;span class="nv"&gt;$pr_fetch_limit&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; number,title,url,files,author,baseRefName,headRefName&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;total_prs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq length &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;gh&lt;/code&gt; only loads 30 open pull requests. For now, we have increased that limit to 100, later we will fix this by giving user the control on this value.&lt;/p&gt;

&lt;p&gt;If you execute this above code, we will get an output similar to this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authorUsername"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"baseRefName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"somebranch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".gitmodules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"additions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"deletions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headRefName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my new feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/user/repo/pull/300"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice, we get all the files changed in a PR inside the &lt;code&gt;files&lt;/code&gt; array. We can simply loop over all the elements of this JSON array to filter our &lt;code&gt;git_file&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find_files_prs&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="nv"&gt;pri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; pri&amp;lt;total_prs&lt;span class="p"&gt;;&lt;/span&gt; pri++ &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;readarray &lt;span class="nt"&gt;-t&lt;/span&gt; changed_files &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.files[].path&lt;span class="o"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;changed_files&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;git_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
          &lt;/span&gt;&lt;span class="nv"&gt;pr_branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.headRefName &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
          &lt;span class="nv"&gt;base_branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.baseRefName &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

          &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s "&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.title &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$pr_branch&lt;/span&gt;&lt;span class="s2"&gt; ➜ &lt;/span&gt;&lt;span class="nv"&gt;$base_branch&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
          &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"by &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.author.login &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"PR Link: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .[&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pri&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;.url &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$all_open_prs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

          &lt;span class="nv"&gt;prs_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;prs_count+1&lt;span class="k"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;fi
  done

  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;$prs_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; 0]]&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Oops!, No pull requests found that modify this file path"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Found &lt;/span&gt;&lt;span class="nv"&gt;$prs_count&lt;/span&gt;&lt;span class="s2"&gt; open pull requests that modify &lt;/span&gt;&lt;span class="nv"&gt;$git_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bunch of things are going on in this piece of code&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We convert our JSON array of changed files path to a bash array &lt;code&gt;changed_files&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We loop over each path to see if it matches the user-provided path or not.&lt;/li&gt;
&lt;li&gt;Furthermore, we output any necessary info we need to, like PR link, base and target branches, and author username.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wait something’s missing right 🤔? What about diffs? The &lt;code&gt;gh pr list&lt;/code&gt; command doesn’t provide the diff data, although we can use a different command &lt;code&gt;gh pr diff&lt;/code&gt; to get diff for a particular PR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;show_diff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

get_diff&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;$show_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 1]]&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Getting diff for PR #&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c"&gt;# filepath with escaped slashes&lt;/span&gt;
    &lt;span class="nv"&gt;clean_filepath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;//\//\\/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    gh &lt;span class="nb"&gt;pr &lt;/span&gt;diff &lt;span class="nt"&gt;--color&lt;/span&gt; always &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"/diff --git a&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="nv"&gt;$clean_filepath&lt;/span&gt;&lt;span class="s2"&gt;/d;/diff/q;p"&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;show_diff&lt;/code&gt; is going to be a global flag that we are gonna control through script arguments. More on that later note that we need to show diff for only 1 file path, which the user provided. To do that we have used &lt;code&gt;sed&lt;/code&gt; to get everything between the pattern &lt;code&gt;1,/diff --git a/&amp;lt;filepath&amp;gt;&lt;/code&gt; and &lt;code&gt;diff&lt;/code&gt; which marks the diff for the next file change.&lt;/p&gt;

&lt;p&gt;It’s time to put everything together. Let’s add some driver code to execute our script correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;getopts&lt;/span&gt; &lt;span class="s2"&gt;"dl:"&lt;/span&gt; o&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
        &lt;/span&gt;d&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPTARG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
              &lt;/span&gt;&lt;span class="nv"&gt;show_diff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
            &lt;span class="k"&gt;fi&lt;/span&gt;
            &lt;span class="p"&gt;;;&lt;/span&gt;
        l&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nv"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPTARG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"You seem to be lost"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
            &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="k"&gt;done

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;pr_fetch_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Using &lt;/span&gt;&lt;span class="nv"&gt;$pr_fetch_limit&lt;/span&gt;&lt;span class="s2"&gt; as PR fetch limit."&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"Using &lt;/span&gt;&lt;span class="nv"&gt;$pr_fetch_limit&lt;/span&gt;&lt;span class="s2"&gt; as default PR fetch limit. Pass the -l flag to modify it"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;fetch_prs
find_files_prs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo &amp;amp; Source
&lt;/h2&gt;

&lt;p&gt;The source is &lt;a href="https://github.com/Bhupesh-V/.Varshney/blob/master/scripts/git/git-prs" rel="noopener noreferrer"&gt;&lt;strong&gt;available here&lt;/strong&gt;&lt;/a&gt; if you want to reference or hack anything. I took the liberty to beautify the output from our script. Here is how the finalized version looks like:&lt;/p&gt;


  


&lt;h2&gt;
  
  
  To-dos &amp;amp; Takeaways
&lt;/h2&gt;

&lt;p&gt;There are a bunch of things that can take this script to the next level&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to provide a permalink to the file change inside a PR, although it's limited by GitHub’s API.&lt;/li&gt;
&lt;li&gt;Support for fetching closed/merged PRs as well using &lt;code&gt;gh pr list --state all&lt;/code&gt; or better asking the user what they want.&lt;/li&gt;
&lt;li&gt;Although we were able to parse out diff for a single file path, I believe it’s a very nasty &amp;amp; error-prone way to do it and ideally, this feature should exist within &lt;code&gt;gh&lt;/code&gt;. I have created an &lt;a href="https://github.com/cli/cli/issues/5398" rel="noopener noreferrer"&gt;&lt;strong&gt;issue&lt;/strong&gt;&lt;/a&gt; to track this, let’s see where it goes.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>bash</category>
    </item>
    <item>
      <title>What are some things that you don't like about git?</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Mon, 18 Apr 2022 17:48:54 +0000</pubDate>
      <link>https://forem.com/bhupesh/what-are-some-things-that-you-dont-like-about-git-ek9</link>
      <guid>https://forem.com/bhupesh/what-are-some-things-that-you-dont-like-about-git-ek9</guid>
      <description>&lt;p&gt;Git has been in the making since 2005, and probably 90% developers use it everyday&lt;br&gt;
What are some pain-points that you face with git everyday or have encountered occasionally?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Leaving the classic, Hitler uses git meme for some gags:)&lt;/em&gt;&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/CDeG4S-mJts"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>git</category>
      <category>discuss</category>
    </item>
    <item>
      <title>One secret tip for first-time OSS contributors. Shh! 🤫 don't tell anyone else</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Mon, 07 Mar 2022 13:59:41 +0000</pubDate>
      <link>https://forem.com/bhupesh/one-secret-tip-for-first-time-oss-contributors-shh-dont-tell-anyone-else-442o</link>
      <guid>https://forem.com/bhupesh/one-secret-tip-for-first-time-oss-contributors-shh-dont-tell-anyone-else-442o</guid>
      <description>&lt;p&gt;I see a lot of folks wandering over the internet asking how to contribute to open-source,&lt;br&gt;
To my disappointment none of the articles, I have read mention what actionable steps should be taken to make your first-time contrib.&lt;/p&gt;

&lt;p&gt;Although I see people giving one generic advice like, &lt;em&gt;"choose a package you already work with and contribute there"&lt;/em&gt;, this still doesn't solve your problem because you might be using packages that are too big, working with these big projects can be sort of overwhelming. &lt;br&gt;
So to make it easier I am gonna share one secret cheat trick with you that is everlasting&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret  🤫
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhs7ibm3g5tzgywg9tyr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhs7ibm3g5tzgywg9tyr.gif" alt="box-of-secrets" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ready? here it is ...&lt;/p&gt;

&lt;h3&gt;
  
  
  "&lt;em&gt;solve static analyzer issues&lt;/em&gt;"
&lt;/h3&gt;

&lt;p&gt;Yes! I am serious here, Every language you work with nowadays has support for tools that do static analysis of code and point out issues. Helping fix these issues is a task everyone sort of ignores or forgets because building features and resolving bugs is much more good (and important to be honest).&lt;/p&gt;

&lt;p&gt;But to have a "head start" in the game, trust me fixing these minor things still counts as a valuable OSS contribution.&lt;br&gt;
Moreover, working on these "code quality cleanup" issues is better than "fixing typos" or waiting around for a "good-first-issue" to open up, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Cool, you have my attention but how?
&lt;/h2&gt;

&lt;p&gt;Any good OSS project also has a bunch of these tools already set up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go ecosystem has &lt;a href="https://goreportcard.com/" rel="noopener noreferrer"&gt;Go Report Card&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Projects based on shell scripting can use &lt;a href="https://github.com/koalaman/shellcheck" rel="noopener noreferrer"&gt;shellcheck&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python has a bunch of tools like &lt;a href="https://github.com/psf/black" rel="noopener noreferrer"&gt;black&lt;/a&gt;, &lt;a href="https://github.com/PyCQA/pylint/" rel="noopener noreferrer"&gt;pylint&lt;/a&gt;, &lt;a href="https://github.com/PyCQA/flake8" rel="noopener noreferrer"&gt;flake8&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Javascript has &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tools like &lt;a href="https://deepsource.io/" rel="noopener noreferrer"&gt;deepsource&lt;/a&gt; has support for finding security related issue for different language stack (which is again just building and finding patterns via static analysis of code)&lt;/li&gt;
&lt;li&gt;Another tool is &lt;a href="https://codeql.github.com/" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt; that detects security issues and gives a grade to the project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a demo run of &lt;a href="https://lgtm.com/" rel="noopener noreferrer"&gt;lgtm&lt;/a&gt; on &lt;a href="https://github.com/pachyderm/pachyderm" rel="noopener noreferrer"&gt;pachyderm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F34342551%2F155185620-3bd85c9e-66ea-46eb-b5ed-1ce35b4e5ea0.png" class="article-body-image-wrapper"&gt;&lt;img alt="lgtm.com demo for a project" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F34342551%2F155185620-3bd85c9e-66ea-46eb-b5ed-1ce35b4e5ea0.png" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run and use these tools over the project you like to contribute to, the probability is there are going to be a bunch of issues reported.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 Pick the ones that are more common in frequency, &lt;/li&gt;
&lt;li&gt;⚠️ report these issues to the project,&lt;/li&gt;
&lt;li&gt;👀 wait for the maintainers to acknowledge it&lt;/li&gt;
&lt;li&gt;🙌🏻 ask to assign the issue to yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Filter out issues that can have a positive impact on code quality, say for e.g replacing single quotes with double quotes is not a good issue in comparison to fixing potential unused variables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If everything works out as expected, congrats! you just took a shortcut to FOSS contribution by opening an issue for yourself, now all you need to do is fix these errors which by now you have already researched about.&lt;/p&gt;

&lt;p&gt;Although do note that using these tools can be sort of a hit-trial approach in finding analyzer warnings that matter. I will say don't misuse this trick for contributions by spamming the repo with them, play the game right&lt;/p&gt;

&lt;p&gt;So what are you waiting for, surf GitHub and send that PR right now!!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post was originally shared on my &lt;a href="https://buttondown.email/bhupesh" rel="noopener noreferrer"&gt;newsletter&lt;/a&gt;, subscribe for more such interesting tips&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>git</category>
      <category>programming</category>
    </item>
    <item>
      <title>View a python function's history over-time with Git &amp; FZF</title>
      <dc:creator>Bhupesh Varshney 👾</dc:creator>
      <pubDate>Mon, 13 Dec 2021 10:14:17 +0000</pubDate>
      <link>https://forem.com/bhupesh/view-a-python-functions-history-over-time-with-git-fzf-47mn</link>
      <guid>https://forem.com/bhupesh/view-a-python-functions-history-over-time-with-git-fzf-47mn</guid>
      <description>&lt;p&gt;I am kinda hooked up on modifying my git terminal workflow and integrate FZF wherever I can, this is such an example script to see changes in a python function overtime in your git history. And yes, its interesting (&amp;amp; stupid)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# FZF Wrapper over git to interactively search code changes inside functions&lt;/span&gt;

readarray &lt;span class="nt"&gt;-t&lt;/span&gt; choices &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;git ls-files | fzf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Choose File: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--height&lt;/span&gt; 40% &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=def ).*?(?=\()'&lt;/span&gt; &lt;span class="nv"&gt;$choices&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | fzf &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ansi&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s2"&gt;"echo {} | xargs -I{} git log --color -L :{}:&lt;/span&gt;&lt;span class="nv"&gt;$choices&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Choose function/method: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--bind&lt;/span&gt; &lt;span class="s1"&gt;'j:down,k:up,ctrl-j:preview-down,ctrl-k:preview-up,ctrl-space:toggle-preview'&lt;/span&gt; &lt;span class="nt"&gt;--preview-window&lt;/span&gt; right:60% &lt;span class="se"&gt;\&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual trick is finding all python functions and methods names in a given file using &lt;code&gt;grep&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=def ).*?(?=\()'&lt;/span&gt; rich/columns.py
__init__
add_renderable
__rich_console__
iter_renderables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we plug the function name with filename in this &lt;code&gt;git&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;-L&lt;/span&gt; :funciton_name:relative_file_path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read more about &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Searching" rel="noopener noreferrer"&gt;git log searching&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a screencast demoing the same for &lt;a href="https://github.com/willmcgugan/rich" rel="noopener noreferrer"&gt;rich&lt;/a&gt; package&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53wp3zotzrxu2qt6ds5u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53wp3zotzrxu2qt6ds5u.gif" alt="git-search-demo-gif" width="600" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are still interested, &lt;a href="https://github.com/Bhupesh-V/.Varshney/blob/master/scripts/git/git-search" rel="noopener noreferrer"&gt;here is the script&lt;/a&gt; to hack around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wish this was scalable to all the programming languages but currently I don't have any clue on how to do this&lt;br&gt;
If you want to contribute help me by adding &lt;code&gt;grep&lt;/code&gt; patterns to do the same search for you favourite programming language. Thanks&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>python</category>
      <category>git</category>
      <category>github</category>
      <category>todayilearned</category>
    </item>
  </channel>
</rss>
