Move entire git-prompt to C
This commit is contained in:
parent
2740f9bc8d
commit
5f2c5b58da
203
git-prompt.c
Normal file
203
git-prompt.c
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#include <ctype.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#if __linux__
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define check(CONDITION) \
|
||||||
|
if (CONDITION) { \
|
||||||
|
fprintf(stderr, "error: %s: %d: %s\n", __FILE__, __LINE__, #CONDITION); \
|
||||||
|
exit(0); \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define check(CONDITION) \
|
||||||
|
if (CONDITION) { \
|
||||||
|
exit(0); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct process {
|
||||||
|
pid_t pid;
|
||||||
|
FILE* out;
|
||||||
|
} process_t;
|
||||||
|
|
||||||
|
process_t process_open(char* command) {
|
||||||
|
int fds[2];
|
||||||
|
check(pipe(fds));
|
||||||
|
int pid = fork();
|
||||||
|
check(pid == -1);
|
||||||
|
if (pid == 0) { // child process
|
||||||
|
close(fds[0]);
|
||||||
|
dup2(fds[1], STDOUT_FILENO);
|
||||||
|
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||||||
|
char* argv[] = {"sh", "-c", command, NULL};
|
||||||
|
exit(execvp(argv[0], argv));
|
||||||
|
} else { // parent process
|
||||||
|
close(fds[1]);
|
||||||
|
process_t process = {pid, fdopen(fds[0], "rb")};
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int process_close(process_t process) {
|
||||||
|
fclose(process.out);
|
||||||
|
int status;
|
||||||
|
check(process.pid != waitpid(process.pid, &status, 0));
|
||||||
|
if (WIFEXITED(status)) {
|
||||||
|
return WEXITSTATUS(status);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* trim(char* str) {
|
||||||
|
char* end;
|
||||||
|
while (isspace((unsigned char)*str)) str++;
|
||||||
|
if (*str == 0) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
end = str + strlen(str) - 1;
|
||||||
|
while (end > str && isspace((unsigned char)*end)) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
end[1] = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* append(char* buffer, int count, ...) {
|
||||||
|
va_list list;
|
||||||
|
int i;
|
||||||
|
va_start(list, count);
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
strcat(buffer, va_arg(list, char*));
|
||||||
|
}
|
||||||
|
va_end(list);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* inttostr(char* buffer, int value) {
|
||||||
|
sprintf(buffer, "%d", value);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// get the current branch name
|
||||||
|
process_t process = process_open("git symbolic-ref --short HEAD");
|
||||||
|
char branch_buf[256] = {};
|
||||||
|
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||||||
|
if (process_close(process)) {
|
||||||
|
// current HEAD is not a symbolic ref
|
||||||
|
process = process_open("git rev-parse --abbrev-ref HEAD");
|
||||||
|
memset(branch_buf, 0, sizeof(branch_buf));
|
||||||
|
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||||||
|
check(process_close(process));
|
||||||
|
if (strcmp("HEAD", trim(branch_buf)) == 0) {
|
||||||
|
// get the commit hash
|
||||||
|
process = process_open("git rev-parse --short HEAD");
|
||||||
|
memset(branch_buf, 0, sizeof(branch_buf));
|
||||||
|
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||||||
|
check(process_close(process));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char* branch = trim(branch_buf);
|
||||||
|
char prompt[1024] = {};
|
||||||
|
append(prompt, 3, " %{%F{66}%}", branch, "%{%f%}");
|
||||||
|
|
||||||
|
// get the upstream remote if one exists
|
||||||
|
char command[1024] = {};
|
||||||
|
append(command, 3, "git config branch.", branch, ".remote");
|
||||||
|
process = process_open(command);
|
||||||
|
char remote_buf[256] = {};
|
||||||
|
fread(remote_buf, 1, sizeof(remote_buf), process.out);
|
||||||
|
if (process_close(process) == 0) {
|
||||||
|
char* remote = trim(remote_buf);
|
||||||
|
// get the number of commits ahead of the remote
|
||||||
|
memset(command, 0, sizeof(command));
|
||||||
|
process = process_open(append(command, 5,
|
||||||
|
"git rev-list --right-only refs/remotes/",
|
||||||
|
remote, "/", branch, "...HEAD --count"));
|
||||||
|
char count[32] = {};
|
||||||
|
fread(count, 1, sizeof(count), process.out);
|
||||||
|
if(process_close(process) == 0 && strcmp("0", trim(count))) {
|
||||||
|
append(prompt, 2, "↑", trim(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the number of commits behind the remote
|
||||||
|
memset(command, 0, sizeof(command));
|
||||||
|
process = process_open(append(command, 5,
|
||||||
|
"git rev-list --left-only refs/remotes/",
|
||||||
|
remote, "/", branch, "...HEAD --count"));
|
||||||
|
memset(count, 0, sizeof(count));
|
||||||
|
fread(count, 1, sizeof(count), process.out);
|
||||||
|
if(process_close(process) == 0 && strcmp("0", trim(count))) {
|
||||||
|
append(prompt, 2, "↓", trim(count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(prompt, 1, " ");
|
||||||
|
|
||||||
|
// get the status and parse it
|
||||||
|
process = process_open("git status --porcelain");
|
||||||
|
char status[2048];
|
||||||
|
int indexed = 0, modified = 0, deleted = 0, untracked = 0, unmerged = 0;
|
||||||
|
while (NULL != fgets(status, sizeof(status) - 1, process.out)) {
|
||||||
|
char X = status[0];
|
||||||
|
char Y = status[1];
|
||||||
|
|
||||||
|
if (X == '?' && Y == '?') {
|
||||||
|
++untracked;
|
||||||
|
} else if ((X == 'A' && (Y == 'A' || Y == 'U')) ||
|
||||||
|
(X == 'D' && (Y == 'D' || Y == 'U')) ||
|
||||||
|
(X == 'U' && (Y == 'A' || Y == 'D' || Y == 'D' || Y == 'U'))) {
|
||||||
|
++unmerged;
|
||||||
|
} else {
|
||||||
|
switch (X) {
|
||||||
|
case ' ':
|
||||||
|
switch (Y) {
|
||||||
|
case 'M': ++modified; break;
|
||||||
|
case 'D': ++deleted; break;
|
||||||
|
} break;
|
||||||
|
case 'D': ++indexed;
|
||||||
|
switch (Y) {
|
||||||
|
case ' ': break;
|
||||||
|
case 'M': ++modified; break;
|
||||||
|
} break;
|
||||||
|
case 'M': case 'A': case 'R': case 'C': ++indexed;
|
||||||
|
switch (Y) {
|
||||||
|
case ' ': break;
|
||||||
|
case 'M': ++modified; break;
|
||||||
|
case 'D': ++deleted; break;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(process_close(process));
|
||||||
|
|
||||||
|
if (indexed || modified || deleted || unmerged || untracked) { // modified
|
||||||
|
char int_buf[32];
|
||||||
|
if (indexed) {
|
||||||
|
append(prompt, 3, "%{%F{2}%}*", inttostr(int_buf, indexed), "%{%f%}");
|
||||||
|
}
|
||||||
|
if (modified) {
|
||||||
|
append(prompt, 3, "%{%F{1}%}+", inttostr(int_buf, modified), "%{%f%}");
|
||||||
|
}
|
||||||
|
if (deleted) {
|
||||||
|
append(prompt, 3, "%{%F{1}%}-", inttostr(int_buf, deleted), "%{%f%}");
|
||||||
|
}
|
||||||
|
if (unmerged) {
|
||||||
|
append(prompt, 3, "%{%B%F{1}%}×", inttostr(int_buf, unmerged), "%{%f%}");
|
||||||
|
}
|
||||||
|
if (untracked) {
|
||||||
|
append(prompt, 1, "%{%F{1}%}…%{%f%}");
|
||||||
|
}
|
||||||
|
} else { // clean
|
||||||
|
append(prompt, 1, "%{%B%F{2}%}✓%{%f%b%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the prompt
|
||||||
|
puts(prompt);
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,17 +1,18 @@
|
|||||||
prompt_fresh_help() {
|
prompt_fresh_help() {
|
||||||
echo "\
|
echo "\
|
||||||
options:
|
options:
|
||||||
-a enable almostontop like behaviour"
|
-a enable almostontop like behaviour
|
||||||
|
-r recompile git-prompt executable"
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_fresh_setup() {
|
prompt_fresh_setup() {
|
||||||
fresh_compile_git_prompt
|
|
||||||
|
|
||||||
# Parse options
|
# Parse options
|
||||||
local almostontop=0
|
local almostontop=0
|
||||||
while getopts 'a' opt; do
|
local recompile=0
|
||||||
|
while getopts 'ar' opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
a) local almostontop=1 ;;
|
a) local almostontop=1 ;;
|
||||||
|
r) local recompile=1 ;;
|
||||||
*) prompt -h fresh; return 1 ;;
|
*) prompt -h fresh; return 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@ -31,6 +32,9 @@ prompt_fresh_setup() {
|
|||||||
add-zsh-hook -d preexec fresh_almostontop
|
add-zsh-hook -d preexec fresh_almostontop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
[ $recompile -eq 1 ] && prompt_cleanup
|
||||||
|
fresh_compile_git_prompt
|
||||||
|
|
||||||
if [ "$USER" = "root" ]; then
|
if [ "$USER" = "root" ]; then
|
||||||
local user="%{%F{1}%}%n%{%f%}"
|
local user="%{%F{1}%}%n%{%f%}"
|
||||||
else
|
else
|
||||||
@ -50,7 +54,7 @@ prompt_fresh_setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prompt_cleanup() {
|
prompt_cleanup() {
|
||||||
if [ -f ~/.cache/zsh/git-prompt ]; then rm ~/.cache/zsh/git-prompt; fi
|
[ -f ~/.cache/zsh/git-prompt ] && rm ~/.cache/zsh/git-prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
fresh_line_one() {
|
fresh_line_one() {
|
||||||
@ -64,58 +68,7 @@ fresh_line_one() {
|
|||||||
local directory="%{%F{37}%}%~%{%f%}"
|
local directory="%{%F{37}%}%~%{%f%}"
|
||||||
|
|
||||||
# Check we are in a git repository
|
# Check we are in a git repository
|
||||||
if `git rev-parse --git-dir > /dev/null 2>&1`; then
|
local git=`~/.cache/zsh/git-prompt`
|
||||||
# Get git branch name & exit early if not found
|
|
||||||
local branch="`git symbolic-ref HEAD 2> /dev/null`"
|
|
||||||
if [ "" = "$branch" ]; then
|
|
||||||
local branch="`git rev-parse --abbrev-ref HEAD`"
|
|
||||||
if [ "HEAD" = "$branch" ]; then
|
|
||||||
local branch="`git rev-parse --short $branch`"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$branch" != "" ]; then
|
|
||||||
local branch=${branch#refs/heads/}
|
|
||||||
# Get the number of commits ahead/behind from the remote branch
|
|
||||||
local remote="`git config branch.$branch.remote 2> /dev/null`"
|
|
||||||
if [ "" != "remote" ]; then
|
|
||||||
local branches="refs/remotes/$remote/$branch...HEAD"
|
|
||||||
local nahead=`git rev-list --right-only $branches --count 2> /dev/null`
|
|
||||||
if [ "0" -ne "$nahead" ]; then local ahead="↑$nahead"; fi
|
|
||||||
local nbehind=`git rev-list --left-only $branches --count 2> /dev/null`
|
|
||||||
if [ "0" -ne "$nbehind" ]; then local behind="↓$nbehind"; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Construct the git prompt
|
|
||||||
local git=" %{%F{66}%}$branch%{%f%}$ahead$behind "
|
|
||||||
|
|
||||||
if [ -f ~/.cache/zsh/git-prompt ]; then
|
|
||||||
# Get the change counts from git-prompt
|
|
||||||
local counts=(`~/.cache/zsh/git-prompt`)
|
|
||||||
|
|
||||||
# Parse the results from git-prompt
|
|
||||||
if [ 0 -eq ${#counts[@]} ]; then # clean
|
|
||||||
local git="$git%{%B%F{2}%}✓%{%f%b%}";
|
|
||||||
else
|
|
||||||
if [ 0 != $counts[1] ]; then # indexed
|
|
||||||
local git="$git%{%F{2}%}*$counts[1]%{%f%}"
|
|
||||||
fi
|
|
||||||
if [ 0 != $counts[2] ]; then # modified
|
|
||||||
local git="$git%{%F{1}%}+$counts[2]%{%f%}";
|
|
||||||
fi
|
|
||||||
if [ 0 != $counts[3] ]; then # deleted
|
|
||||||
local git="$git%{%F{1}%}-$counts[3]%{%f%}";
|
|
||||||
fi
|
|
||||||
if [ 0 != $counts[4] ]; then # unmerged
|
|
||||||
local git="$git%{%B%F{1}%}×$counts[4]%{%f%b%}";
|
|
||||||
fi
|
|
||||||
if [ 0 != $counts[5] ]; then # untracked
|
|
||||||
local git="$git%{%F{1}%}…%{%f%}";
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If the last command failed, display its error code at the right
|
# If the last command failed, display its error code at the right
|
||||||
if [[ $exit_code -ne 0 ]]; then
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
@ -145,62 +98,11 @@ fresh_almostontop() {
|
|||||||
fresh_compile_git_prompt() {
|
fresh_compile_git_prompt() {
|
||||||
# Compile a simple C program which parses the output of `git status
|
# Compile a simple C program which parses the output of `git status
|
||||||
# --procelain` to greatly decrease the time taken to draw the prompt
|
# --procelain` to greatly decrease the time taken to draw the prompt
|
||||||
local cache=~/.cache/zsh; [ ! -d $cache ] && mkdir -p $cache
|
[ ! -d ~/.cache/zsh ] && mkdir -p ~/.cache/zsh
|
||||||
if [ ! -f $cache/git-prompt ]; then
|
if [ ! -f ~/.cache/zsh/git-prompt ]; then
|
||||||
cc -x c -std=gnu99 -O3 -DNDEBUG -o $cache/git-prompt - 2> /dev/null << EOF
|
cc -std=gnu99 -O3 -DNDEBUG ~/.config/zsh/git-prompt.c -o ~/.cache/zsh/git-prompt
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
FILE *file = popen("git status --porcelain", "r");
|
|
||||||
if (NULL == file) { return 0; }
|
|
||||||
|
|
||||||
char status[2048];
|
|
||||||
int indexed = 0, modified = 0, deleted = 0, untracked = 0, unmerged = 0;
|
|
||||||
|
|
||||||
while (NULL != fgets(status, sizeof(status) - 1, file)) {
|
|
||||||
char X = status[0];
|
|
||||||
char Y = status[1];
|
|
||||||
|
|
||||||
if (X == '?' && Y == '?') {
|
|
||||||
++untracked;
|
|
||||||
} else if ((X == 'A' && (Y == 'A' || Y == 'U')) ||
|
|
||||||
(X == 'D' && (Y == 'D' || Y == 'U')) ||
|
|
||||||
(X == 'U' && (Y == 'A' || Y == 'D' || Y == 'D' || Y == 'U'))) {
|
|
||||||
++unmerged;
|
|
||||||
} else {
|
|
||||||
switch (X) {
|
|
||||||
case ' ':
|
|
||||||
switch (Y) {
|
|
||||||
case 'M': ++modified; break;
|
|
||||||
case 'D': ++deleted; break;
|
|
||||||
} break;
|
|
||||||
case 'D': ++indexed;
|
|
||||||
switch (Y) {
|
|
||||||
case ' ': break;
|
|
||||||
case 'M': ++modified; break;
|
|
||||||
} break;
|
|
||||||
case 'M': case 'A': case 'R': case 'C': ++indexed;
|
|
||||||
switch (Y) {
|
|
||||||
case ' ': break;
|
|
||||||
case 'M': ++modified; break;
|
|
||||||
case 'D': ++deleted; break;
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pclose(file);
|
|
||||||
|
|
||||||
if (indexed || modified || deleted || unmerged || untracked) {
|
|
||||||
printf("%d %d %d %d %d", indexed, modified, deleted, unmerged, untracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "git prompt disabled, are the system development headers installed?"
|
echo "git-prompt was not compiled, is a C99 toolchain installed?"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user