Skip to content

Commit

Permalink
Remove timeout for VCS status output
Browse files Browse the repository at this point in the history
This was promptoglyph's raison d'être, but modern Git has an "fsmonitor"
feature that gets you _really_ fast repo status by using a daemon
(like Watchman) to keep track of which files might have changed and need
another stat(). This system also doesn't like if you murder `git status`
while it's in the middle of doing its thing.

With this feature gone, I should probably revisit zsh's `vcs_info` and
see how it compares (both in output and performance) to this tool.
  • Loading branch information
mrkline committed Jul 29, 2019
1 parent 7e9a169 commit ba7ae12
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 88 deletions.
86 changes: 15 additions & 71 deletions git.d
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import vcs;

// Fetches information about the Git repository,
// or returns null if we are not in one.
RepoStatus* getRepoStatus(Duration allottedTime)
RepoStatus* getRepoStatus()
{
import std.parallelism;

Expand All @@ -31,49 +31,30 @@ RepoStatus* getRepoStatus(Duration allottedTime)

RepoStatus* ret = new RepoStatus;

ret.head = getHead(repoRoot, allottedTime);
ret.head = getHead(repoRoot);

ret.flags = asyncGetFlags(allottedTime);
ret.flags = getFlags();

return ret;
}


private:

/// Uses asynchronous I/O to read as much git status output as it can
/// in the given amount of time.
public // So std.parallelism can get at it
StatusFlags asyncGetFlags(Duration allottedTime)
StatusFlags getFlags()
{
// Currently we can only do this for Unix.
// Windows async pipe I/O (they call it "overlapped" I/O)
// is more... involved.
// TODO: Either write a Windows implementation or suck it up
// and do things synchronously in Windows.
import core.sys.posix.poll;

StatusFlags ret;

// Light off git status while we find the HEAD
auto pipes = pipeProcess(["git", "status", "--porcelain"], Redirect.stdout);
// If an exception gets thrown, be sure to cleanup the process.
scope(failure) {
kill(pipes.pid);
wait(pipes.pid);
}

// Local function for processing the output of git status.
// See the docs for git status porcelain output
void processPorcelainLine(string line)
void processPorcelainLine(const char[] line)
{
if (line is null)
return;

// git status --porcelain spits out a two-character code
// for each file that would show up in Git status
// Why is this .array needed? Check odd set.back error below
string set = line[0 .. 2];
const char[] set = line[0 .. 2];

// Question marks indicate a file is untracked.
if (set.canFind('?')) {
Expand All @@ -94,54 +75,24 @@ StatusFlags asyncGetFlags(Duration allottedTime)
}
}

// We need the actual file descriptor of the pipe so we can call poll
immutable int fdes = fileno(pipes.stdout.getFP());
enforce(fdes >= 0, "fileno failed.");

pollfd pfd;
pfd.fd = fdes; // The file descriptor we want to poll
pfd.events = POLLIN; // Notify us if there is data to be read

string nextLine;

// As long as git status is running, keep at it.
while (!tryWait(pipes.pid).terminated) {

// Poll the pipe with an arbitrary 5 millisecond timeout
enforce(poll(&pfd, 1, 5) >= 0, "poll failed");

// If we have data to read, process a line of it.
if (pfd.revents & POLLIN) {
nextLine = pipes.stdout.readln();
processPorcelainLine(nextLine);
}
else if (pastTime(allottedTime)) {
import core.sys.posix.signal: SIGTERM;
kill(pipes.pid, SIGTERM);
break;
}
// Light off git status while we find the HEAD
auto pipes = pipeProcess(["git", "status", "--porcelain"], Redirect.stdout);
scope (failure) {
kill(pipes.pid);
}

// Process anything left over
while (nextLine !is null) {
nextLine = pipes.stdout.readln();
processPorcelainLine(nextLine);
scope (exit) {
wait(pipes.pid);
}

// Join the process
wait(pipes.pid);
foreach (line; pipes.stdout.byLine) processPorcelainLine(line);

return ret;
}

/// Gets the name of the current Git head, or a shortened SHA
/// if there is no symbolic name.
string getHead(string repoRoot, Duration allottedTime)
string getHead(string repoRoot)
{
// getHead doesn't use async I/O because it is assumed that
// reading one-line files will take a negligible amount of time.
// If this assumption proves false, we should revisit it.

// NOTE(dkg): added check to allow for git submodules
// check if the .git file/folder is actually a folder
// if it is a file, we are in a submodule
Expand Down Expand Up @@ -180,18 +131,13 @@ string getHead(string repoRoot, Duration allottedTime)
ret = searchTagsForHead(tagsPath, headSHA);
if (!ret.empty)
return relativePath(ret, tagsPath);
else if (pastTime(allottedTime))
return headSHA[0 .. 7];

// No need to check heads as we handled that case above.
// Let's check remotes
immutable remotesPath = buildPath(refsPath, "remotes");
ret = searchDirectoryForHead(remotesPath, headSHA);
if (!ret.empty)
return relativePath(ret, remotesPath);
else if (pastTime(allottedTime))
return headSHA[0 .. 7];


// We didn't find anything in remotes. Let's check packed-refs
immutable packedRefsPath = buildPath(repoRoot, ".git", "packed-refs");
Expand All @@ -214,12 +160,10 @@ string getHead(string repoRoot, Duration allottedTime)

if (sha == headSHA)
return refPath.baseName.idup;
else if (pastTime(allottedTime))
return headSHA[0 .. 7];
}
}

// Still nothing. Just return a shortened version of the HEAD sha
// Still nothing. Just return a shortened version of the HEAD SHA
return headSHA[0 .. 7];
}

Expand Down
21 changes: 4 additions & 17 deletions promptoglyph-vcs.d
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module promptoglyph.vcs;

import std.getopt;
import std.datetime : msecs, Duration;
import std.range : empty;
import std.stdio : write;

Expand All @@ -23,7 +22,6 @@ void main(string[] args)
{
markProgramStart();

uint timeLimit = 500;
bool noColor;
bool bash, zsh;
StatusStringOptions stringOptions;
Expand All @@ -34,7 +32,6 @@ void main(string[] args)
config.bundling,
"help|h", { writeAndSucceed(helpString); },
"version|v", { writeAndSucceed(versionString); },
"time-limit|t", &timeLimit,
"prefix|p", &stringOptions.prefix,
"indexed-text|i", &stringOptions.indexedText,
"modified-text|m", &stringOptions.modifiedText,
Expand All @@ -59,15 +56,12 @@ void main(string[] args)
else // Redundant (none is the default), but more explicit.
escapesToUse = Escapes.none;

const Duration allottedTime = timeLimit.msecs;

const RepoStatus* status = getRepoStatus(allottedTime);
const RepoStatus* status = getRepoStatus();

string statusString = stringRepOfStatus(
status, stringOptions,
noColor ? UseColor.no : UseColor.yes,
escapesToUse,
allottedTime);
escapesToUse);

write(statusString);
}
Expand All @@ -76,15 +70,12 @@ void main(string[] args)
* Gets a string representation of the status of the Git repo
*
* Params:
* allottedTime = The amount of time given to gather Git info.
* Git status will be killed if it does not complete in this much time.
* Since this is for a shell prompt, responsiveness is important.
* colors = Whether or not colored output is desired
* escapes = Whether or not ZSH escapes are needed. Ignored if no colors are desired.
*
*/
string stringRepOfStatus(const RepoStatus* status, const ref StatusStringOptions stringOptions,
UseColor colors, Escapes escapes, Duration allottedTime)
UseColor colors, Escapes escapes)
{
import time;

Expand Down Expand Up @@ -122,9 +113,6 @@ string stringRepOfStatus(const RepoStatus* status, const ref StatusStringOptions
string ret = head ~ flags ~
colorText(stringOptions.suffix, &resetColor);

if (pastTime(allottedTime))
ret = "T " ~ ret;

return stringOptions.prefix ~ ret;
}

Expand Down Expand Up @@ -191,8 +179,7 @@ if you are currently in one and nothing otherwise. Output looks like
[master ✔±?]
where "master" is the current branch, ? indicates untracked files,
± indicates changed but unstaged files, and ✔ indicates files staged
in the index. If "git status" could not run in a timely manner to get this info
(see --time-limit above), a T is placed in front.
in the index.
Future plans include additional info (like when merging),
and possibly Subversion and Mercurial support.
EOS";

1 comment on commit ba7ae12

@rr0gi
Copy link

@rr0gi rr0gi commented on ba7ae12 Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove --time-limit from --help output, it is confusing :]

Please sign in to comment.