summaryrefslogtreecommitdiff
path: root/notes.c
blob: bd737842d9eb9da747e613111117f2c8bd0b24ea (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
150
151
152
153
154
155
156
157
158
159
160
#include "cache.h"
#include "commit.h"
#include "notes.h"
#include "refs.h"
#include "utf8.h"
#include "strbuf.h"
#include "tree-walk.h"

struct entry {
	unsigned char commit_sha1[20];
	unsigned char notes_sha1[20];
};

struct hash_map {
	struct entry *entries;
	off_t count, size;
};

static int initialized;
static struct hash_map hash_map;

static int hash_index(struct hash_map *map, const unsigned char *sha1)
{
	int i = ((*(unsigned int *)sha1) % map->size);

	for (;;) {
		unsigned char *current = map->entries[i].commit_sha1;

		if (!hashcmp(sha1, current))
			return i;

		if (is_null_sha1(current))
			return -1 - i;

		if (++i == map->size)
			i = 0;
	}
}

static void add_entry(const unsigned char *commit_sha1,
		const unsigned char *notes_sha1)
{
	int index;

	if (hash_map.count + 1 > hash_map.size >> 1) {
		int i, old_size = hash_map.size;
		struct entry *old = hash_map.entries;

		hash_map.size = old_size ? old_size << 1 : 64;
		hash_map.entries = (struct entry *)
			xcalloc(sizeof(struct entry), hash_map.size);

		for (i = 0; i < old_size; i++)
			if (!is_null_sha1(old[i].commit_sha1)) {
				index = -1 - hash_index(&hash_map,
						old[i].commit_sha1);
				memcpy(hash_map.entries + index, old + i,
					sizeof(struct entry));
			}
		free(old);
	}

	index = hash_index(&hash_map, commit_sha1);
	if (index < 0) {
		index = -1 - index;
		hash_map.count++;
	}

	hashcpy(hash_map.entries[index].commit_sha1, commit_sha1);
	hashcpy(hash_map.entries[index].notes_sha1, notes_sha1);
}

static void initialize_hash_map(const char *notes_ref_name)
{
	unsigned char sha1[20], commit_sha1[20];
	unsigned mode;
	struct tree_desc desc;
	struct name_entry entry;
	void *buf;

	if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
	    get_tree_entry(commit_sha1, "", sha1, &mode))
		return;

	buf = fill_tree_descriptor(&desc, sha1);
	if (!buf)
		die("Could not read %s for notes-index", sha1_to_hex(sha1));

	while (tree_entry(&desc, &entry))
		if (!get_sha1(entry.path, commit_sha1))
			add_entry(commit_sha1, entry.sha1);
	free(buf);
}

static unsigned char *lookup_notes(const unsigned char *commit_sha1)
{
	int index;

	if (!hash_map.size)
		return NULL;

	index = hash_index(&hash_map, commit_sha1);
	if (index < 0)
		return NULL;
	return hash_map.entries[index].notes_sha1;
}

void get_commit_notes(const struct commit *commit, struct strbuf *sb,
		const char *output_encoding)
{
	static const char *utf8 = "utf-8";
	unsigned char *sha1;
	char *msg, *msg_p;
	unsigned long linelen, msglen;
	enum object_type type;

	if (!initialized) {
		const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);
		if (env)
			notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
		else if (!notes_ref_name)
			notes_ref_name = GIT_NOTES_DEFAULT_REF;
		initialize_hash_map(notes_ref_name);
		initialized = 1;
	}

	sha1 = lookup_notes(commit->object.sha1);
	if (!sha1)
		return;

	if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||
			type != OBJ_BLOB)
		return;

	if (output_encoding && *output_encoding &&
			strcmp(utf8, output_encoding)) {
		char *reencoded = reencode_string(msg, output_encoding, utf8);
		if (reencoded) {
			free(msg);
			msg = reencoded;
			msglen = strlen(msg);
		}
	}

	/* we will end the annotation by a newline anyway */
	if (msglen && msg[msglen - 1] == '\n')
		msglen--;

	strbuf_addstr(sb, "\nNotes:\n");

	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
		linelen = strchrnul(msg_p, '\n') - msg_p;

		strbuf_addstr(sb, "    ");
		strbuf_add(sb, msg_p, linelen);
		strbuf_addch(sb, '\n');
	}

	free(msg);
}