What your commit history says about you.
[TELLS ] 1 address of yours, and not a forwarding address
s*************1@gmail.com
8 commits, 2019-04-01 to 2019-04-08
[TELLS ] Identities of yours that share a timezone
all of these commit from UTC+2:
s*m <s*m@users.noreply.github.com>
S***********a <s*************1@gmail.com>
[TELLS ] Days your clock was somewhere else
UTC-4 2024-08-14 2024-08-16
[NARROWS] The hours you commit at
18:00 ########################## 60
20:00 ########################## 60
21:00 ########################## 60
22:00 ############################## 68
23:00 .
You have never committed between 23:00 and 11:00. A gap that
long across 250 commits is not chance, it is a sleep schedule.
Every commit you have ever pushed carries your name, your address, and the timezone the clock was set to on the machine that wrote it. A few hundred of them draw the hours you are awake and the part of the world you are in.
Alibi reads that back to you.
Git does not store the time in UTC. It stores the local time and the
offset, on every single commit, forever. 2024-08-14T15:20:00-04:00 is not
a timestamp, it is a statement about where you were sitting that afternoon.
Plenty of tools will draw you a chart of your commit hours. They present it as a fun fact about how much of a night owl you are. It is also:
- the timezone you live in, on every commit, unhidden by anything;
- the dates you were somewhere else, if you ever committed from a trip;
- the hours you sleep, which barely move for most people over years;
- and taken together, enough to match two accounts you thought were separate.
That last one is the point of the tool. If you keep a work identity and a personal one apart, or a real name and a handle, the addresses are the part you remember to change. The timezone and the sleep schedule are the part you cannot change, and they are in both.
| Addresses | Every address you have committed under, including the ones you stopped using years ago, with the dates they were in use |
| Names | Every name, and whether it reads as a full legal name rather than a handle |
| Correlation | Identities of yours that share a timezone, which is how separate accounts get tied together |
| Timezones | Where your clock was set, told apart from daylight saving, with the dates of anything that was not home |
| Hours | The histogram, and whether the quiet stretch in it is really a sleep schedule or just a small sample |
| Days | Whether this looks like a job or something you do around one |
| Messages | Home directory paths, addresses typed into a body, and the people you have named in trailers |
It works out which of the people in the history are you, so on a repository
with a hundred contributors you get a report about yourself rather than a
dump of the mailing list. Pass --me if it guesses wrong.
- It never writes. Not to the repository, not anywhere. A test reads the source and fails the build if a call that writes a file appears in it.
- It never opens a socket. There is no network code in it, and the same test fails if any appears.
- It runs git, and only git. Reading
.gitby hand would have been a few hundred lines of packfile handling to avoid depending on something already installed. So it shells out, from one file, through a list of five exact invocations, none of which can change a repository.git config --get user.emailis on the list;git configis not. - It masks what it finds. Enough of an address to recognise which of your
own it is, not enough to scrape out of a report you pasted somewhere. Use
--revealwhen you want the whole thing.
alibialibi ~/code/thing ~/code/otheralibi --me you@example.com --revealExit code is 1 when the history names you, places you, or links two of your identities, 2 when it could not be read, 0 when there is nothing to say. That makes it usable from a script.
A shallow clone has had its oldest commits cut off, and those are the ones most likely to carry an address you have since stopped using. A repository with nine commits in it cannot tell you anything about your working hours.
Alibi says so, in its own section, at the top, before any of the findings. A short report is not a clean history, and a tool that quietly gives up is worse than no tool: it hands you confidence you have not earned.
The same rule runs through the checks. Twenty empty hours out of six commits is not a sleep schedule, it is six commits, so the report does not claim one until the gap is bigger than chance would explain.
Less than you would like, and the report says so rather than pretending otherwise.
Going forward is easy. Set a forwarding address, set a name you are happy to publish, and do it on every machine you commit from, which is the part people forget.
Going back is not. Rewriting history changes the hash of every commit after the one you touched, which breaks every fork, every open pull request, and every link anybody has ever posted to a line of your code. Anyone who already cloned it still has the old commits. For a repository nobody else has touched it is worth doing. For anything else, know what it costs first.
The timezone is worse: there is no setting for it. Git reads the clock. The only way not to publish it is to commit from a machine set to UTC, and that is its own tell, because almost nobody does.
Needs Go and nothing else. No dependencies at all.
go build -o alibi .go test ./...The tests build real repositories with real git and read them back, so what is under test is what git writes rather than my idea of it.
It reports; it does not rewrite. git filter-repo exists and is better at
that than anything I would write, and the decision to run it should be a
decision, not a flag on an audit tool.
It reads commits. Tags, notes and the reflog carry names and dates too, and they are not in here yet.
MIT. Do what you like with it.