summaryrefslogtreecommitdiff
path: root/datever.c
blob: 3e348cdc9083f7f8cd48e32d553d8915303b5528 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <stdio.h>
#include <time.h>

#include <git2.h>


struct datever
{
	int year;
	int month;
	int day;
	int revision;
};


static int must(const char* msg, int err);
static int is_dirty(git_repository* repo);
static struct datever find_latest(git_repository* repo);
static struct datever datever_init();
static struct datever datever_today();
static struct datever datever_from_time(time_t time);
static int datever_is_valid(const struct datever* ver);
static int datever_compare(const struct datever* lhs, const struct datever* rhs);


int
main()
{
	git_libgit2_init();
	git_repository* repo = NULL;
	must("open repository", git_repository_open_ext(&repo, NULL, GIT_REPOSITORY_OPEN_FROM_ENV, NULL));
	const struct datever ver = find_latest(repo);
	printf("%d%02d%02d.%d\n", ver.year, ver.month, ver.day, ver.revision);
	git_libgit2_shutdown();
}


/// If [[err]] is considered erroneous, print [[msg]] and the last libgit2 error to standard error and then terminate
/// current process.
int
must(const char* const msg, const int err)
{
	if (0 > err) {
		const git_error* const e = git_error_last();
		if (NULL != msg)
			dprintf(2, "%s: ", msg);
		dprintf(2, "%s\n", e->message);
		exit(1);
	}
	return err;
}


int
is_dirty(git_repository* repo)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
	git_status_options statopts = GIT_STATUS_OPTIONS_INIT;
#pragma GCC diagnostic pop
	statopts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
	statopts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
	git_status_list* status;
	must("retrieve status", git_status_list_new(&status, repo, &statopts));
	const int count = git_status_list_entrycount(status);
	git_status_list_free(status);
	return count > 0;
}


struct datever
find_latest(git_repository* const repo)
{
	git_revwalk* walker = NULL;
	must("walk revisions", git_revwalk_new(&walker, repo));
	must("find HEAD", git_revwalk_push_head(walker));
	git_oid oid;
	git_commit* commit;
	struct datever prev = is_dirty(repo) ? datever_today() : datever_init();
	struct datever ver = datever_init();
	while (0 == git_revwalk_next(&oid, walker)) {
		must("find commit", git_commit_lookup(&commit, repo, &oid));
		const git_signature* author = git_commit_author(commit);
		ver = datever_from_time(author->when.time);
		if (datever_is_valid(&prev)) {
			if (0 == datever_compare(&prev, &ver))
				ver.revision += prev.revision;
			else break;
		}
		prev = ver;
		git_commit_free(commit);
	}
	git_commit_free(commit);
	return prev;
}


/// Initialize empty, and thus invalid, version.
struct datever
datever_init()
{
	return (struct datever){
		.year = -1,
		.month = -1,
		.day = -1,
		.revision = -1,
	};
}


struct datever
datever_today()
{
	return datever_from_time(time(NULL));
}


struct datever
datever_from_time(const time_t time)
{
	const struct tm* tm = gmtime(&time);
	return (struct datever){
		.year = tm->tm_year + 1900,
		.month = tm->tm_mon + 1,
		.day = tm->tm_mday,
		.revision = 1,
	};
}


int
datever_is_valid(const struct datever* const ver)
{
	return 0 <= ver->year && 0 <= ver->month && 0 <= ver->day && 0 <= ver->revision;
}


/// Compares two versions date-wise ignoring revision count. Returns similarly to strcmp(3).
int
datever_compare(const struct datever* const lhs, const struct datever* const rhs)
{
	if (lhs->year > rhs->year) return -1;
	if (lhs->year < rhs->year) return 1;
	if (lhs->month > rhs->month) return -1;
	if (lhs->month < rhs->month) return 1;
	if (lhs->day > rhs->day) return -1;
	if (lhs->day < rhs->day) return 1;
	return 0;
}