Milestone 2 of the gita project: git integration
This is the second milestone where we extract and list detailed information for repos. The other posts in this series are
- overview
- milestone 1: basic CLI
- milestone 2: git integration
- milestone 3: git delegation
- milestone 4: speedup
- milestone 5: miscellaneous topics
v0.1.1: add only git repo paths
In milestone 1, we an add any folder to repo_path
as long as it exists.
In reality, we should only add git repo folders.
To identify a repo folder, we can check the existence of .git
in the repo root folder:
regular repos have a .git
folder and repos generated with git worktree
or
git submodule
have a .git
file.
Alternatively, we can use the git rev-parse --is-inside-work-tree
command.
Unlike the simple method which requires the project root path as input, this
command can take sub-folders too. The down side is the speed.
Make sure to add tests as you code along, so that the test coverage doesn’t drop significantly over the commits. Strive for > 90% coverage if possible.
v0.1.2: add ll
sub-command
The main feature of milestone 2 is the gita ll
sub-command, which displays detailed
information of all repos, such as branch name, relationship between local and
remote branches, edit status, commit message, etc. This is part of R in the
CRUD API.
All the information items come from executing git commands. For example
- branch name:
git rev-parse --abbrev-ref HEAD
- commit message:
git show-branch --no-name HEAD
We will implement these two items in this commit and the rest in later commits.
Make sure to try them in your terminal.
If your git
version is really old, some of them may not work.
To execute terminal command inside Python code, we will use the subprocess module. We can execute the git command with
cmd = 'git show-branch --no-name HEAD'.split()
result = subprocess.run(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
universal_newlines=True,
cwd=path)
Here the standard output is redirected and saved in result.stdout
,
and the standard error is thrown away.
The cwd
keyword controls where the cmd
is run.
The repo path
comes from the helper function get_repos()
in milestone 1.
In the end, the expected output looks something like
$ gita ll
> repo1 master fix some bug
> my-repo new-feature create some new feature here
> xxx master refactor some code
For Python text formatting, take a look at this article.
v0.1.3: add edit status symbols
I use the git auto-completion script in my shell environment, and my terminal looks like
chronos (master *+) _drafts $
Here chronos
is my user name, _draft
is the basename of my current path,
and the content in parentheses shows the git repo’s branch name and edit status.
Specifically,
*
: unstaged change+
: staged change
In this session, we will add these edit status symbols next to the branch names.
We will also add a _
symbol for untracked files.
The corresponding git commands are
- unstaged changes:
git diff --quiet
- staged changes:
git diff --quiet --cached
- untracked files:
git ls-files -zo --exclude-standard
Note that the information is in the returncode
for the first two commands.
For the third command, we need to check the existence of stdout
.
v0.1.4: add local and remote relationship color
Another useful information is the relationship between local and remote branches. In this commit, we will encode it in the color of the branch name. The color codes are
- white: local has no remote
- green: local is the same as remote
- red: local has diverged from remote
- purple: local is ahead of remote (good for push)
- yellow: local is behind remote (good for merge)
The first two situations can be distinguished by the command
git diff --quiet @{u} @{0}
where @{u}
denotes remote (i.e., upstream) and @{0}
denotes local head.
If there is no remote branch, the returncode
is 128.
If local is identical to the remote, the returncode
is 0.
If some difference exists, the returncode
is 1.
Unfortunately the returncode
doesn’t distinguish the other three situations.
To further distinguished them, we need to first find out the commit where the local and remote branches agree. And then compare the local and remote heads to this diverging point. The git command for the common commit is
git merge-base @{0} @{u}
Then we can use git diff --quiet
two more times to tell apart the three situations.
v0.2: clean up and tag
This completes milestone 2. At this point, you can optionally tag the code base using
git tag v0.2