mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-29 17:05:49 +00:00
Merge pull request #1662 from arrbee/examples-like-git
Command line status example (with bug fixes)
This commit is contained in:
commit
f2d110f168
@ -3,7 +3,7 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
|
CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
|
||||||
LFLAGS = -L../build -lgit2 -lz
|
LFLAGS = -L../build -lgit2 -lz
|
||||||
APPS = general showindex diff rev-list cat-file
|
APPS = general showindex diff rev-list cat-file status
|
||||||
|
|
||||||
all: $(APPS)
|
all: $(APPS)
|
||||||
|
|
||||||
|
443
examples/status.c
Normal file
443
examples/status.c
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||||
|
* a Linking Exception. For full terms see the included COPYING file.
|
||||||
|
*/
|
||||||
|
#include <git2.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
FORMAT_DEFAULT = 0,
|
||||||
|
FORMAT_LONG = 1,
|
||||||
|
FORMAT_SHORT = 2,
|
||||||
|
FORMAT_PORCELAIN = 3,
|
||||||
|
};
|
||||||
|
#define MAX_PATHSPEC 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This example demonstrates the use of the libgit2 status APIs,
|
||||||
|
* particularly the `git_status_list` object, to roughly simulate the
|
||||||
|
* output of running `git status`. It serves as a simple example of
|
||||||
|
* using those APIs to get basic status information.
|
||||||
|
*
|
||||||
|
* This does not have:
|
||||||
|
* - Robust error handling
|
||||||
|
* - Colorized or paginated output formatting
|
||||||
|
*
|
||||||
|
* This does have:
|
||||||
|
* - Examples of translating command line arguments to the status
|
||||||
|
* options settings to mimic `git status` results.
|
||||||
|
* - A sample status formatter that matches the default "long" format
|
||||||
|
* from `git status`
|
||||||
|
* - A sample status formatter that matches the "short" format
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void check(int error, const char *message, const char *extra)
|
||||||
|
{
|
||||||
|
const git_error *lg2err;
|
||||||
|
const char *lg2msg = "", *lg2spacer = "";
|
||||||
|
|
||||||
|
if (!error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
|
||||||
|
lg2msg = lg2err->message;
|
||||||
|
lg2spacer = " - ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra)
|
||||||
|
fprintf(stderr, "%s '%s' [%d]%s%s\n",
|
||||||
|
message, extra, error, lg2spacer, lg2msg);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "%s [%d]%s%s\n",
|
||||||
|
message, error, lg2spacer, lg2msg);
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fail(const char *message)
|
||||||
|
{
|
||||||
|
check(-1, message, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_branch(git_repository *repo, int format)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
const char *branch = NULL;
|
||||||
|
git_reference *head = NULL;
|
||||||
|
|
||||||
|
error = git_repository_head(&head, repo);
|
||||||
|
|
||||||
|
if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
|
||||||
|
branch = NULL;
|
||||||
|
else if (!error) {
|
||||||
|
branch = git_reference_name(head);
|
||||||
|
if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
|
||||||
|
branch += strlen("refs/heads/");
|
||||||
|
} else
|
||||||
|
check(error, "failed to get current branch", NULL);
|
||||||
|
|
||||||
|
if (format == FORMAT_LONG)
|
||||||
|
printf("# On branch %s\n",
|
||||||
|
branch ? branch : "Not currently on any branch.");
|
||||||
|
else
|
||||||
|
printf("## %s\n", branch ? branch : "HEAD (no branch)");
|
||||||
|
|
||||||
|
git_reference_free(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_long(git_repository *repo, git_status_list *status)
|
||||||
|
{
|
||||||
|
size_t i, maxi = git_status_list_entrycount(status);
|
||||||
|
const git_status_entry *s;
|
||||||
|
int header = 0, changes_in_index = 0;
|
||||||
|
int changed_in_workdir = 0, rm_in_workdir = 0;
|
||||||
|
const char *old_path, *new_path;
|
||||||
|
|
||||||
|
(void)repo;
|
||||||
|
|
||||||
|
/* print index changes */
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
char *istatus = NULL;
|
||||||
|
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_CURRENT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_WT_DELETED)
|
||||||
|
rm_in_workdir = 1;
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_INDEX_NEW)
|
||||||
|
istatus = "new file: ";
|
||||||
|
if (s->status & GIT_STATUS_INDEX_MODIFIED)
|
||||||
|
istatus = "modified: ";
|
||||||
|
if (s->status & GIT_STATUS_INDEX_DELETED)
|
||||||
|
istatus = "deleted: ";
|
||||||
|
if (s->status & GIT_STATUS_INDEX_RENAMED)
|
||||||
|
istatus = "renamed: ";
|
||||||
|
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
|
||||||
|
istatus = "typechange:";
|
||||||
|
|
||||||
|
if (istatus == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!header) {
|
||||||
|
printf("# Changes to be committed:\n");
|
||||||
|
printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
|
||||||
|
printf("#\n");
|
||||||
|
header = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_path = s->head_to_index->old_file.path;
|
||||||
|
new_path = s->head_to_index->new_file.path;
|
||||||
|
|
||||||
|
if (old_path && new_path && strcmp(old_path, new_path))
|
||||||
|
printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
|
||||||
|
else
|
||||||
|
printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
changes_in_index = 1;
|
||||||
|
printf("#\n");
|
||||||
|
}
|
||||||
|
header = 0;
|
||||||
|
|
||||||
|
/* print workdir changes to tracked files */
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
char *wstatus = NULL;
|
||||||
|
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_WT_MODIFIED)
|
||||||
|
wstatus = "modified: ";
|
||||||
|
if (s->status & GIT_STATUS_WT_DELETED)
|
||||||
|
wstatus = "deleted: ";
|
||||||
|
if (s->status & GIT_STATUS_WT_RENAMED)
|
||||||
|
wstatus = "renamed: ";
|
||||||
|
if (s->status & GIT_STATUS_WT_TYPECHANGE)
|
||||||
|
wstatus = "typechange:";
|
||||||
|
|
||||||
|
if (wstatus == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!header) {
|
||||||
|
printf("# Changes not staged for commit:\n");
|
||||||
|
printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
|
||||||
|
printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
|
||||||
|
printf("#\n");
|
||||||
|
header = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_path = s->index_to_workdir->old_file.path;
|
||||||
|
new_path = s->index_to_workdir->new_file.path;
|
||||||
|
|
||||||
|
if (old_path && new_path && strcmp(old_path, new_path))
|
||||||
|
printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
|
||||||
|
else
|
||||||
|
printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
changed_in_workdir = 1;
|
||||||
|
printf("#\n");
|
||||||
|
}
|
||||||
|
header = 0;
|
||||||
|
|
||||||
|
/* print untracked files */
|
||||||
|
|
||||||
|
header = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_WT_NEW) {
|
||||||
|
|
||||||
|
if (!header) {
|
||||||
|
printf("# Untracked files:\n");
|
||||||
|
printf("# (use \"git add <file>...\" to include in what will be committed)\n");
|
||||||
|
printf("#\n");
|
||||||
|
header = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("#\t%s\n", s->index_to_workdir->old_file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header = 0;
|
||||||
|
|
||||||
|
/* print ignored files */
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_IGNORED) {
|
||||||
|
|
||||||
|
if (!header) {
|
||||||
|
printf("# Ignored files:\n");
|
||||||
|
printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
|
||||||
|
printf("#\n");
|
||||||
|
header = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("#\t%s\n", s->index_to_workdir->old_file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changes_in_index && changed_in_workdir)
|
||||||
|
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_short(git_repository *repo, git_status_list *status)
|
||||||
|
{
|
||||||
|
size_t i, maxi = git_status_list_entrycount(status);
|
||||||
|
const git_status_entry *s;
|
||||||
|
char istatus, wstatus;
|
||||||
|
const char *extra, *a, *b, *c;
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_CURRENT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
a = b = c = NULL;
|
||||||
|
istatus = wstatus = ' ';
|
||||||
|
extra = "";
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_INDEX_NEW)
|
||||||
|
istatus = 'A';
|
||||||
|
if (s->status & GIT_STATUS_INDEX_MODIFIED)
|
||||||
|
istatus = 'M';
|
||||||
|
if (s->status & GIT_STATUS_INDEX_DELETED)
|
||||||
|
istatus = 'D';
|
||||||
|
if (s->status & GIT_STATUS_INDEX_RENAMED)
|
||||||
|
istatus = 'R';
|
||||||
|
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
|
||||||
|
istatus = 'T';
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_WT_NEW) {
|
||||||
|
if (istatus == ' ')
|
||||||
|
istatus = '?';
|
||||||
|
wstatus = '?';
|
||||||
|
}
|
||||||
|
if (s->status & GIT_STATUS_WT_MODIFIED)
|
||||||
|
wstatus = 'M';
|
||||||
|
if (s->status & GIT_STATUS_WT_DELETED)
|
||||||
|
wstatus = 'D';
|
||||||
|
if (s->status & GIT_STATUS_WT_RENAMED)
|
||||||
|
wstatus = 'R';
|
||||||
|
if (s->status & GIT_STATUS_WT_TYPECHANGE)
|
||||||
|
wstatus = 'T';
|
||||||
|
|
||||||
|
if (s->status & GIT_STATUS_IGNORED) {
|
||||||
|
istatus = '!';
|
||||||
|
wstatus = '!';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (istatus == '?' && wstatus == '?')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s->index_to_workdir &&
|
||||||
|
s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
|
||||||
|
{
|
||||||
|
git_submodule *sm = NULL;
|
||||||
|
unsigned int smstatus = 0;
|
||||||
|
|
||||||
|
if (!git_submodule_lookup(
|
||||||
|
&sm, repo, s->index_to_workdir->new_file.path) &&
|
||||||
|
!git_submodule_status(&smstatus, sm))
|
||||||
|
{
|
||||||
|
if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
|
||||||
|
extra = " (new commits)";
|
||||||
|
else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
|
||||||
|
extra = " (modified content)";
|
||||||
|
else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
|
||||||
|
extra = " (modified content)";
|
||||||
|
else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
|
||||||
|
extra = " (untracked content)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->head_to_index) {
|
||||||
|
a = s->head_to_index->old_file.path;
|
||||||
|
b = s->head_to_index->new_file.path;
|
||||||
|
}
|
||||||
|
if (s->index_to_workdir) {
|
||||||
|
if (!a)
|
||||||
|
a = s->index_to_workdir->old_file.path;
|
||||||
|
if (!b)
|
||||||
|
b = s->index_to_workdir->old_file.path;
|
||||||
|
c = s->index_to_workdir->new_file.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (istatus == 'R') {
|
||||||
|
if (wstatus == 'R')
|
||||||
|
printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
|
||||||
|
else
|
||||||
|
printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
|
||||||
|
} else {
|
||||||
|
if (wstatus == 'R')
|
||||||
|
printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
|
||||||
|
else
|
||||||
|
printf("%c%c %s%s\n", istatus, wstatus, a, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < maxi; ++i) {
|
||||||
|
s = git_status_byindex(status, i);
|
||||||
|
|
||||||
|
if (s->status == GIT_STATUS_WT_NEW)
|
||||||
|
printf("?? %s\n", s->index_to_workdir->old_file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
git_repository *repo = NULL;
|
||||||
|
int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0;
|
||||||
|
git_status_options opt = GIT_STATUS_OPTIONS_INIT;
|
||||||
|
git_status_list *status;
|
||||||
|
char *repodir = ".", *pathspec[MAX_PATHSPEC];
|
||||||
|
|
||||||
|
opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
|
||||||
|
opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
|
||||||
|
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
|
||||||
|
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
|
||||||
|
|
||||||
|
for (i = 1; i < argc; ++i) {
|
||||||
|
if (argv[i][0] != '-') {
|
||||||
|
if (npaths < MAX_PATHSPEC)
|
||||||
|
pathspec[npaths++] = argv[i];
|
||||||
|
else
|
||||||
|
fail("Example only supports a limited pathspec");
|
||||||
|
}
|
||||||
|
else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short"))
|
||||||
|
format = FORMAT_SHORT;
|
||||||
|
else if (!strcmp(argv[i], "--long"))
|
||||||
|
format = FORMAT_LONG;
|
||||||
|
else if (!strcmp(argv[i], "--porcelain"))
|
||||||
|
format = FORMAT_PORCELAIN;
|
||||||
|
else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch"))
|
||||||
|
showbranch = 1;
|
||||||
|
else if (!strcmp(argv[i], "-z")) {
|
||||||
|
zterm = 1;
|
||||||
|
if (format == FORMAT_DEFAULT)
|
||||||
|
format = FORMAT_PORCELAIN;
|
||||||
|
}
|
||||||
|
else if (!strcmp(argv[i], "--ignored"))
|
||||||
|
opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
|
||||||
|
else if (!strcmp(argv[i], "-uno") ||
|
||||||
|
!strcmp(argv[i], "--untracked-files=no"))
|
||||||
|
opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
|
||||||
|
else if (!strcmp(argv[i], "-unormal") ||
|
||||||
|
!strcmp(argv[i], "--untracked-files=normal"))
|
||||||
|
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
|
||||||
|
else if (!strcmp(argv[i], "-uall") ||
|
||||||
|
!strcmp(argv[i], "--untracked-files=all"))
|
||||||
|
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
|
||||||
|
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
|
||||||
|
else if (!strcmp(argv[i], "--ignore-submodules=all"))
|
||||||
|
opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
|
||||||
|
else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir=")))
|
||||||
|
repodir = argv[i] + strlen("--git-dir=");
|
||||||
|
else
|
||||||
|
check(-1, "Unsupported option", argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format == FORMAT_DEFAULT)
|
||||||
|
format = FORMAT_LONG;
|
||||||
|
if (format == FORMAT_LONG)
|
||||||
|
showbranch = 1;
|
||||||
|
if (npaths > 0) {
|
||||||
|
opt.pathspec.strings = pathspec;
|
||||||
|
opt.pathspec.count = npaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to open the repository at the given path (or at the current
|
||||||
|
* directory if none was given).
|
||||||
|
*/
|
||||||
|
check(git_repository_open_ext(&repo, repodir, 0, NULL),
|
||||||
|
"Could not open repository", repodir);
|
||||||
|
|
||||||
|
if (git_repository_is_bare(repo))
|
||||||
|
fail("Cannot report status on bare repository");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run status on the repository
|
||||||
|
*
|
||||||
|
* Because we want to simluate a full "git status" run and want to
|
||||||
|
* support some command line options, we use `git_status_foreach_ext()`
|
||||||
|
* instead of just the plain status call. This allows (a) iterating
|
||||||
|
* over the index and then the workdir and (b) extra flags that control
|
||||||
|
* which files are included. If you just want simple status (e.g. to
|
||||||
|
* enumerate files that are modified) then you probably don't need the
|
||||||
|
* extended API.
|
||||||
|
*/
|
||||||
|
check(git_status_list_new(&status, repo, &opt),
|
||||||
|
"Could not get status", NULL);
|
||||||
|
|
||||||
|
if (showbranch)
|
||||||
|
show_branch(repo, format);
|
||||||
|
|
||||||
|
if (format == FORMAT_LONG)
|
||||||
|
print_long(repo, status);
|
||||||
|
else
|
||||||
|
print_short(repo, status);
|
||||||
|
|
||||||
|
git_status_list_free(status);
|
||||||
|
git_repository_free(repo);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -111,6 +111,12 @@ typedef enum {
|
|||||||
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename
|
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename
|
||||||
* detection should be run between the index and the working directory
|
* detection should be run between the index and the working directory
|
||||||
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
|
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
|
||||||
|
* - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
|
||||||
|
* sensitivity for the file system and forces the output to be in
|
||||||
|
* case-sensitive order
|
||||||
|
* - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
|
||||||
|
* sensitivity for the file system and forces the output to be in
|
||||||
|
* case-insensitive order
|
||||||
*
|
*
|
||||||
* Calling `git_status_foreach()` is like calling the extended version
|
* Calling `git_status_foreach()` is like calling the extended version
|
||||||
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
|
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
|
||||||
@ -127,6 +133,8 @@ typedef enum {
|
|||||||
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
|
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
|
||||||
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
|
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
|
||||||
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
|
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
|
||||||
|
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
|
||||||
|
GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
|
||||||
} git_status_opt_t;
|
} git_status_opt_t;
|
||||||
|
|
||||||
#define GIT_STATUS_OPT_DEFAULTS \
|
#define GIT_STATUS_OPT_DEFAULTS \
|
||||||
|
@ -498,7 +498,7 @@ int git_attr_assignment__parse(
|
|||||||
|
|
||||||
assert(assigns && !assigns->length);
|
assert(assigns && !assigns->length);
|
||||||
|
|
||||||
assigns->_cmp = sort_by_hash_and_name;
|
git_vector_set_cmp(assigns, sort_by_hash_and_name);
|
||||||
|
|
||||||
while (*scan && *scan != '\n') {
|
while (*scan && *scan != '\n') {
|
||||||
const char *name_start, *value_start;
|
const char *name_start, *value_start;
|
||||||
|
25
src/diff.c
25
src/diff.c
@ -365,7 +365,7 @@ static git_diff_list *diff_list_alloc(
|
|||||||
diff->pfxcomp = git__prefixcmp_icase;
|
diff->pfxcomp = git__prefixcmp_icase;
|
||||||
diff->entrycomp = git_index_entry__cmp_icase;
|
diff->entrycomp = git_index_entry__cmp_icase;
|
||||||
|
|
||||||
diff->deltas._cmp = git_diff_delta__casecmp;
|
git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
@ -786,10 +786,15 @@ static int diff_scan_inside_untracked_dir(
|
|||||||
|
|
||||||
/* need to recurse into non-ignored directories */
|
/* need to recurse into non-ignored directories */
|
||||||
if (!is_ignored && S_ISDIR(info->nitem->mode)) {
|
if (!is_ignored && S_ISDIR(info->nitem->mode)) {
|
||||||
if ((error = git_iterator_advance_into(
|
error = git_iterator_advance_into(&info->nitem, info->new_iter);
|
||||||
&info->nitem, info->new_iter)) < 0)
|
|
||||||
break;
|
if (!error)
|
||||||
continue;
|
continue;
|
||||||
|
else if (error == GIT_ENOTFOUND) {
|
||||||
|
error = 0;
|
||||||
|
is_ignored = true; /* treat empty as ignored */
|
||||||
|
} else
|
||||||
|
break; /* real error, must stop */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* found a non-ignored item - treat parent dir as untracked */
|
/* found a non-ignored item - treat parent dir as untracked */
|
||||||
@ -1160,7 +1165,7 @@ int git_diff_tree_to_index(
|
|||||||
d->pfxcomp = git__prefixcmp_icase;
|
d->pfxcomp = git__prefixcmp_icase;
|
||||||
d->entrycomp = git_index_entry__cmp_icase;
|
d->entrycomp = git_index_entry__cmp_icase;
|
||||||
|
|
||||||
d->deltas._cmp = git_diff_delta__casecmp;
|
git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp);
|
||||||
git_vector_sort(&d->deltas);
|
git_vector_sort(&d->deltas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1261,10 +1266,10 @@ int git_diff__paired_foreach(
|
|||||||
/* force case-sensitive delta sort */
|
/* force case-sensitive delta sort */
|
||||||
if (icase_mismatch) {
|
if (icase_mismatch) {
|
||||||
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
||||||
head2idx->deltas._cmp = git_diff_delta__cmp;
|
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
|
||||||
git_vector_sort(&head2idx->deltas);
|
git_vector_sort(&head2idx->deltas);
|
||||||
} else {
|
} else {
|
||||||
idx2wd->deltas._cmp = git_diff_delta__cmp;
|
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp);
|
||||||
git_vector_sort(&idx2wd->deltas);
|
git_vector_sort(&idx2wd->deltas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1296,10 +1301,10 @@ int git_diff__paired_foreach(
|
|||||||
/* restore case-insensitive delta sort */
|
/* restore case-insensitive delta sort */
|
||||||
if (icase_mismatch) {
|
if (icase_mismatch) {
|
||||||
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
||||||
head2idx->deltas._cmp = git_diff_delta__casecmp;
|
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
|
||||||
git_vector_sort(&head2idx->deltas);
|
git_vector_sort(&head2idx->deltas);
|
||||||
} else {
|
} else {
|
||||||
idx2wd->deltas._cmp = git_diff_delta__casecmp;
|
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp);
|
||||||
git_vector_sort(&idx2wd->deltas);
|
git_vector_sort(&idx2wd->deltas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/index.c
10
src/index.c
@ -290,16 +290,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case)
|
|||||||
{
|
{
|
||||||
index->ignore_case = ignore_case;
|
index->ignore_case = ignore_case;
|
||||||
|
|
||||||
index->entries._cmp = ignore_case ? index_icmp : index_cmp;
|
|
||||||
index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
|
index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
|
||||||
index->entries_search = ignore_case ? index_isrch : index_srch;
|
index->entries_search = ignore_case ? index_isrch : index_srch;
|
||||||
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
|
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
|
||||||
index->entries.sorted = 0;
|
|
||||||
|
git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp);
|
||||||
git_vector_sort(&index->entries);
|
git_vector_sort(&index->entries);
|
||||||
|
|
||||||
index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;
|
|
||||||
index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
|
index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
|
||||||
index->reuc.sorted = 0;
|
|
||||||
|
git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp);
|
||||||
git_vector_sort(&index->reuc);
|
git_vector_sort(&index->reuc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2024,7 +2024,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
|
|||||||
|
|
||||||
git_vector_sort(&index->entries);
|
git_vector_sort(&index->entries);
|
||||||
|
|
||||||
entries._cmp = index->entries._cmp;
|
git_vector_set_cmp(&entries, index->entries._cmp);
|
||||||
git_vector_swap(&entries, &index->entries);
|
git_vector_swap(&entries, &index->entries);
|
||||||
|
|
||||||
git_index_clear(index);
|
git_index_clear(index);
|
||||||
|
12
src/status.c
12
src/status.c
@ -335,8 +335,16 @@ int git_status_list_new(
|
|||||||
status->head2idx, status->idx2wd, status_collect, status)) < 0)
|
status->head2idx, status->idx2wd, status_collect, status)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 ||
|
if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
|
||||||
(flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0)
|
git_vector_set_cmp(&status->paired, status_entry_cmp);
|
||||||
|
if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
|
||||||
|
git_vector_set_cmp(&status->paired, status_entry_icmp);
|
||||||
|
|
||||||
|
if ((flags &
|
||||||
|
(GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
|
||||||
|
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
|
||||||
|
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
|
||||||
|
GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
|
||||||
git_vector_sort(&status->paired);
|
git_vector_sort(&status->paired);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
@ -151,7 +151,7 @@ int git_submodule_foreach(
|
|||||||
int error;
|
int error;
|
||||||
git_submodule *sm;
|
git_submodule *sm;
|
||||||
git_vector seen = GIT_VECTOR_INIT;
|
git_vector seen = GIT_VECTOR_INIT;
|
||||||
seen._cmp = submodule_cmp;
|
git_vector_set_cmp(&seen, submodule_cmp);
|
||||||
|
|
||||||
assert(repo && callback);
|
assert(repo && callback);
|
||||||
|
|
||||||
|
@ -78,4 +78,13 @@ void git_vector_remove_matching(
|
|||||||
int git_vector_resize_to(git_vector *v, size_t new_length);
|
int git_vector_resize_to(git_vector *v, size_t new_length);
|
||||||
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
|
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
|
||||||
|
|
||||||
|
/** Set the comparison function used for sorting the vector */
|
||||||
|
GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp)
|
||||||
|
{
|
||||||
|
if (cmp != v->_cmp) {
|
||||||
|
v->_cmp = cmp;
|
||||||
|
v->sorted = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1109,6 +1109,26 @@ void test_diff_workdir__untracked_directory_scenarios(void)
|
|||||||
|
|
||||||
git_diff_list_free(diff);
|
git_diff_list_free(diff);
|
||||||
|
|
||||||
|
/* empty directory in empty directory */
|
||||||
|
|
||||||
|
cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777));
|
||||||
|
|
||||||
|
memset(&exp, 0, sizeof(exp));
|
||||||
|
exp.names = files1;
|
||||||
|
|
||||||
|
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
|
||||||
|
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
|
||||||
|
|
||||||
|
cl_assert_equal_i(4, exp.files);
|
||||||
|
cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
|
||||||
|
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
|
||||||
|
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
|
||||||
|
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
|
||||||
|
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
|
||||||
|
|
||||||
|
git_diff_list_free(diff);
|
||||||
|
|
||||||
/* directory with only ignored files */
|
/* directory with only ignored files */
|
||||||
|
|
||||||
cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777));
|
cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777));
|
||||||
|
@ -6,6 +6,9 @@ int cb_status__normal(
|
|||||||
{
|
{
|
||||||
status_entry_counts *counts = payload;
|
status_entry_counts *counts = payload;
|
||||||
|
|
||||||
|
if (counts->debug)
|
||||||
|
cb_status__print(path, status_flags, NULL);
|
||||||
|
|
||||||
if (counts->entry_count >= counts->expected_entry_count) {
|
if (counts->entry_count >= counts->expected_entry_count) {
|
||||||
counts->wrong_status_flags_count++;
|
counts->wrong_status_flags_count++;
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -8,6 +8,7 @@ typedef struct {
|
|||||||
const unsigned int* expected_statuses;
|
const unsigned int* expected_statuses;
|
||||||
const char** expected_paths;
|
const char** expected_paths;
|
||||||
int expected_entry_count;
|
int expected_entry_count;
|
||||||
|
bool debug;
|
||||||
} status_entry_counts;
|
} status_entry_counts;
|
||||||
|
|
||||||
/* cb_status__normal takes payload of "status_entry_counts *" */
|
/* cb_status__normal takes payload of "status_entry_counts *" */
|
||||||
|
@ -743,3 +743,83 @@ void test_status_worktree__simple_delete_indexed(void)
|
|||||||
GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
|
GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
|
||||||
git_status_list_free(status);
|
git_status_list_free(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *icase_paths[] = { "B", "c", "g", "H" };
|
||||||
|
static unsigned int icase_statuses[] = {
|
||||||
|
GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
|
||||||
|
GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *case_paths[] = { "B", "H", "c", "g" };
|
||||||
|
static unsigned int case_statuses[] = {
|
||||||
|
GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
|
||||||
|
GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED,
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_status_worktree__sorting_by_case(void)
|
||||||
|
{
|
||||||
|
git_repository *repo = cl_git_sandbox_init("icase");
|
||||||
|
git_index *index;
|
||||||
|
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
||||||
|
bool native_ignore_case;
|
||||||
|
status_entry_counts counts;
|
||||||
|
|
||||||
|
cl_git_pass(git_repository_index(&index, repo));
|
||||||
|
native_ignore_case =
|
||||||
|
(git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
|
||||||
|
git_index_free(index);
|
||||||
|
|
||||||
|
memset(&counts, 0, sizeof(counts));
|
||||||
|
counts.expected_entry_count = 0;
|
||||||
|
counts.expected_paths = NULL;
|
||||||
|
counts.expected_statuses = NULL;
|
||||||
|
cl_git_pass(
|
||||||
|
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
|
||||||
|
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||||
|
|
||||||
|
cl_git_rewritefile("icase/B", "new stuff");
|
||||||
|
cl_must_pass(p_unlink("icase/c"));
|
||||||
|
cl_git_rewritefile("icase/g", "new stuff");
|
||||||
|
cl_must_pass(p_unlink("icase/H"));
|
||||||
|
|
||||||
|
memset(&counts, 0, sizeof(counts));
|
||||||
|
counts.expected_entry_count = 4;
|
||||||
|
if (native_ignore_case) {
|
||||||
|
counts.expected_paths = icase_paths;
|
||||||
|
counts.expected_statuses = icase_statuses;
|
||||||
|
} else {
|
||||||
|
counts.expected_paths = case_paths;
|
||||||
|
counts.expected_statuses = case_statuses;
|
||||||
|
}
|
||||||
|
cl_git_pass(
|
||||||
|
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
|
||||||
|
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||||
|
|
||||||
|
opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
|
||||||
|
|
||||||
|
memset(&counts, 0, sizeof(counts));
|
||||||
|
counts.expected_entry_count = 4;
|
||||||
|
counts.expected_paths = case_paths;
|
||||||
|
counts.expected_statuses = case_statuses;
|
||||||
|
cl_git_pass(
|
||||||
|
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
|
||||||
|
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||||
|
|
||||||
|
opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
|
||||||
|
|
||||||
|
memset(&counts, 0, sizeof(counts));
|
||||||
|
counts.expected_entry_count = 4;
|
||||||
|
counts.expected_paths = icase_paths;
|
||||||
|
counts.expected_statuses = icase_statuses;
|
||||||
|
cl_git_pass(
|
||||||
|
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
|
||||||
|
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||||
|
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user