Merge branch 'en/merge-recursive-directory-rename-fixes'
[git] / builtin / patch-id.c
1 #include "cache.h"
2 #include "builtin.h"
3 #include "config.h"
4 #include "diff.h"
5
6 static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
7 {
8         char name[GIT_MAX_HEXSZ + 1];
9
10         if (!patchlen)
11                 return;
12
13         memcpy(name, oid_to_hex(id), the_hash_algo->hexsz + 1);
14         printf("%s %s\n", oid_to_hex(result), name);
15 }
16
17 static int remove_space(char *line)
18 {
19         char *src = line;
20         char *dst = line;
21         unsigned char c;
22
23         while ((c = *src++) != '\0') {
24                 if (!isspace(c))
25                         *dst++ = c;
26         }
27         return dst - line;
28 }
29
30 static int scan_hunk_header(const char *p, int *p_before, int *p_after)
31 {
32         static const char digits[] = "0123456789";
33         const char *q, *r;
34         int n;
35
36         q = p + 4;
37         n = strspn(q, digits);
38         if (q[n] == ',') {
39                 q += n + 1;
40                 n = strspn(q, digits);
41         }
42         if (n == 0 || q[n] != ' ' || q[n+1] != '+')
43                 return 0;
44
45         r = q + n + 2;
46         n = strspn(r, digits);
47         if (r[n] == ',') {
48                 r += n + 1;
49                 n = strspn(r, digits);
50         }
51         if (n == 0)
52                 return 0;
53
54         *p_before = atoi(q);
55         *p_after = atoi(r);
56         return 1;
57 }
58
59 static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
60                            struct strbuf *line_buf, int stable)
61 {
62         int patchlen = 0, found_next = 0;
63         int before = -1, after = -1;
64         git_hash_ctx ctx;
65
66         the_hash_algo->init_fn(&ctx);
67         oidclr(result);
68
69         while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
70                 char *line = line_buf->buf;
71                 const char *p = line;
72                 int len;
73
74                 if (!skip_prefix(line, "diff-tree ", &p) &&
75                     !skip_prefix(line, "commit ", &p) &&
76                     !skip_prefix(line, "From ", &p) &&
77                     starts_with(line, "\\ ") && 12 < strlen(line))
78                         continue;
79
80                 if (!get_oid_hex(p, next_oid)) {
81                         found_next = 1;
82                         break;
83                 }
84
85                 /* Ignore commit comments */
86                 if (!patchlen && !starts_with(line, "diff "))
87                         continue;
88
89                 /* Parsing diff header?  */
90                 if (before == -1) {
91                         if (starts_with(line, "index "))
92                                 continue;
93                         else if (starts_with(line, "--- "))
94                                 before = after = 1;
95                         else if (!isalpha(line[0]))
96                                 break;
97                 }
98
99                 /* Looking for a valid hunk header?  */
100                 if (before == 0 && after == 0) {
101                         if (starts_with(line, "@@ -")) {
102                                 /* Parse next hunk, but ignore line numbers.  */
103                                 scan_hunk_header(line, &before, &after);
104                                 continue;
105                         }
106
107                         /* Split at the end of the patch.  */
108                         if (!starts_with(line, "diff "))
109                                 break;
110
111                         /* Else we're parsing another header.  */
112                         if (stable)
113                                 flush_one_hunk(result, &ctx);
114                         before = after = -1;
115                 }
116
117                 /* If we get here, we're inside a hunk.  */
118                 if (line[0] == '-' || line[0] == ' ')
119                         before--;
120                 if (line[0] == '+' || line[0] == ' ')
121                         after--;
122
123                 /* Compute the sha without whitespace */
124                 len = remove_space(line);
125                 patchlen += len;
126                 the_hash_algo->update_fn(&ctx, line, len);
127         }
128
129         if (!found_next)
130                 oidclr(next_oid);
131
132         flush_one_hunk(result, &ctx);
133
134         return patchlen;
135 }
136
137 static void generate_id_list(int stable)
138 {
139         struct object_id oid, n, result;
140         int patchlen;
141         struct strbuf line_buf = STRBUF_INIT;
142
143         oidclr(&oid);
144         while (!feof(stdin)) {
145                 patchlen = get_one_patchid(&n, &result, &line_buf, stable);
146                 flush_current_id(patchlen, &oid, &result);
147                 oidcpy(&oid, &n);
148         }
149         strbuf_release(&line_buf);
150 }
151
152 static const char patch_id_usage[] = "git patch-id [--stable | --unstable]";
153
154 static int git_patch_id_config(const char *var, const char *value, void *cb)
155 {
156         int *stable = cb;
157
158         if (!strcmp(var, "patchid.stable")) {
159                 *stable = git_config_bool(var, value);
160                 return 0;
161         }
162
163         return git_default_config(var, value, cb);
164 }
165
166 int cmd_patch_id(int argc, const char **argv, const char *prefix)
167 {
168         int stable = -1;
169
170         git_config(git_patch_id_config, &stable);
171
172         /* If nothing is set, default to unstable. */
173         if (stable < 0)
174                 stable = 0;
175
176         if (argc == 2 && !strcmp(argv[1], "--stable"))
177                 stable = 1;
178         else if (argc == 2 && !strcmp(argv[1], "--unstable"))
179                 stable = 0;
180         else if (argc != 1)
181                 usage(patch_id_usage);
182
183         generate_id_list(stable);
184         return 0;
185 }