1 #include "common.h"
2 #include "diff-simple.h"
3 #include "utils.h"
4 #include "log.h"
5
6 DiffEntry *
diff_entry_new(char type,char status,unsigned char * sha1,const char * name)7 diff_entry_new (char type, char status, unsigned char *sha1, const char *name)
8 {
9 DiffEntry *de = g_new0 (DiffEntry, 1);
10
11 de->type = type;
12 de->status = status;
13 memcpy (de->sha1, sha1, 20);
14 de->name = g_strdup(name);
15
16 return de;
17 }
18
19 DiffEntry *
diff_entry_new_from_dirent(char type,char status,SeafDirent * dent,const char * basedir)20 diff_entry_new_from_dirent (char type, char status,
21 SeafDirent *dent, const char *basedir)
22 {
23 DiffEntry *de = g_new0 (DiffEntry, 1);
24 unsigned char sha1[20];
25 char *path;
26
27 hex_to_rawdata (dent->id, sha1, 20);
28 path = g_strconcat (basedir, dent->name, NULL);
29
30 de->type = type;
31 de->status = status;
32 memcpy (de->sha1, sha1, 20);
33 de->name = path;
34 de->size = dent->size;
35
36 #ifdef SEAFILE_CLIENT
37 if (type == DIFF_TYPE_COMMITS &&
38 (status == DIFF_STATUS_ADDED ||
39 status == DIFF_STATUS_MODIFIED ||
40 status == DIFF_STATUS_DIR_ADDED ||
41 status == DIFF_STATUS_DIR_DELETED)) {
42 de->mtime = dent->mtime;
43 de->mode = dent->mode;
44 de->modifier = g_strdup(dent->modifier);
45 }
46 #endif
47
48 return de;
49 }
50
51 void
diff_entry_free(DiffEntry * de)52 diff_entry_free (DiffEntry *de)
53 {
54 g_free (de->name);
55 if (de->new_name)
56 g_free (de->new_name);
57
58 #ifdef SEAFILE_CLIENT
59 g_free (de->modifier);
60 #endif
61
62 g_free (de);
63 }
64
65 inline static gboolean
dirent_same(SeafDirent * denta,SeafDirent * dentb)66 dirent_same (SeafDirent *denta, SeafDirent *dentb)
67 {
68 return (strcmp (dentb->id, denta->id) == 0 &&
69 denta->mode == dentb->mode &&
70 denta->mtime == dentb->mtime);
71 }
72
73 static int
diff_files(int n,SeafDirent * dents[],const char * basedir,DiffOptions * opt)74 diff_files (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)
75 {
76 SeafDirent *files[3];
77 int i, n_files = 0;
78
79 memset (files, 0, sizeof(files[0])*n);
80 for (i = 0; i < n; ++i) {
81 if (dents[i] && S_ISREG(dents[i]->mode)) {
82 files[i] = dents[i];
83 ++n_files;
84 }
85 }
86
87 if (n_files == 0)
88 return 0;
89
90 return opt->file_cb (n, basedir, files, opt->data);
91 }
92
93 static int
94 diff_trees_recursive (int n, SeafDir *trees[],
95 const char *basedir, DiffOptions *opt);
96
97 static int
diff_directories(int n,SeafDirent * dents[],const char * basedir,DiffOptions * opt)98 diff_directories (int n, SeafDirent *dents[], const char *basedir, DiffOptions *opt)
99 {
100 SeafDirent *dirs[3];
101 int i, n_dirs = 0;
102 char *dirname = "";
103 int ret;
104 SeafDir *sub_dirs[3], *dir;
105
106 memset (dirs, 0, sizeof(dirs[0])*n);
107 for (i = 0; i < n; ++i) {
108 if (dents[i] && S_ISDIR(dents[i]->mode)) {
109 dirs[i] = dents[i];
110 ++n_dirs;
111 }
112 }
113
114 if (n_dirs == 0)
115 return 0;
116
117 gboolean recurse = TRUE;
118 ret = opt->dir_cb (n, basedir, dirs, opt->data, &recurse);
119 if (ret < 0)
120 return ret;
121
122 if (!recurse)
123 return 0;
124
125 memset (sub_dirs, 0, sizeof(sub_dirs[0])*n);
126 for (i = 0; i < n; ++i) {
127 if (dents[i] != NULL && S_ISDIR(dents[i]->mode)) {
128 dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
129 opt->store_id,
130 opt->version,
131 dents[i]->id);
132 if (!dir) {
133 seaf_warning ("Failed to find dir %s:%s.\n",
134 opt->store_id, dents[i]->id);
135 ret = -1;
136 goto free_sub_dirs;
137 }
138 sub_dirs[i] = dir;
139
140 dirname = dents[i]->name;
141 }
142 }
143
144 char *new_basedir = g_strconcat (basedir, dirname, "/", NULL);
145
146 ret = diff_trees_recursive (n, sub_dirs, new_basedir, opt);
147
148 g_free (new_basedir);
149
150 free_sub_dirs:
151 for (i = 0; i < n; ++i)
152 seaf_dir_free (sub_dirs[i]);
153 return ret;
154 }
155
156 static int
diff_trees_recursive(int n,SeafDir * trees[],const char * basedir,DiffOptions * opt)157 diff_trees_recursive (int n, SeafDir *trees[],
158 const char *basedir, DiffOptions *opt)
159 {
160 GList *ptrs[3];
161 SeafDirent *dents[3];
162 int i;
163 SeafDirent *dent;
164 char *first_name;
165 gboolean done;
166 int ret = 0;
167
168 for (i = 0; i < n; ++i) {
169 if (trees[i])
170 ptrs[i] = trees[i]->entries;
171 else
172 ptrs[i] = NULL;
173 }
174
175 while (1) {
176 first_name = NULL;
177 memset (dents, 0, sizeof(dents[0])*n);
178 done = TRUE;
179
180 /* Find the "largest" name, assuming dirents are sorted. */
181 for (i = 0; i < n; ++i) {
182 if (ptrs[i] != NULL) {
183 done = FALSE;
184 dent = ptrs[i]->data;
185 if (!first_name)
186 first_name = dent->name;
187 else if (strcmp(dent->name, first_name) > 0)
188 first_name = dent->name;
189 }
190 }
191
192 if (done)
193 break;
194
195 /*
196 * Setup dir entries for all names that equal to first_name
197 */
198 for (i = 0; i < n; ++i) {
199 if (ptrs[i] != NULL) {
200 dent = ptrs[i]->data;
201 if (strcmp(first_name, dent->name) == 0) {
202 dents[i] = dent;
203 ptrs[i] = ptrs[i]->next;
204 }
205 }
206 }
207
208 if (n == 2 && dents[0] && dents[1] && dirent_same(dents[0], dents[1]))
209 continue;
210
211 if (n == 3 && dents[0] && dents[1] && dents[2] &&
212 dirent_same(dents[0], dents[1]) && dirent_same(dents[0], dents[2]))
213 continue;
214
215 /* Diff files of this level. */
216 ret = diff_files (n, dents, basedir, opt);
217 if (ret < 0)
218 return ret;
219
220 /* Recurse into sub level. */
221 ret = diff_directories (n, dents, basedir, opt);
222 if (ret < 0)
223 return ret;
224 }
225
226 return ret;
227 }
228
229 int
diff_trees(int n,const char * roots[],DiffOptions * opt)230 diff_trees (int n, const char *roots[], DiffOptions *opt)
231 {
232 SeafDir **trees, *root;
233 int i, ret;
234
235 g_return_val_if_fail (n == 2 || n == 3, -1);
236
237 trees = g_new0 (SeafDir *, n);
238 for (i = 0; i < n; ++i) {
239 root = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
240 opt->store_id,
241 opt->version,
242 roots[i]);
243 if (!root) {
244 seaf_warning ("Failed to find dir %s:%s.\n", opt->store_id, roots[i]);
245 g_free (trees);
246 return -1;
247 }
248 trees[i] = root;
249 }
250
251 ret = diff_trees_recursive (n, trees, "", opt);
252
253 for (i = 0; i < n; ++i)
254 seaf_dir_free (trees[i]);
255 g_free (trees);
256
257 return ret;
258 }
259
260 typedef struct DiffData {
261 GList **results;
262 gboolean fold_dir_diff;
263 } DiffData;
264
265 static int
twoway_diff_files(int n,const char * basedir,SeafDirent * files[],void * vdata)266 twoway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)
267 {
268 DiffData *data = vdata;
269 GList **results = data->results;
270 DiffEntry *de;
271 SeafDirent *tree1 = files[0];
272 SeafDirent *tree2 = files[1];
273
274 if (!tree1) {
275 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
276 tree2, basedir);
277 *results = g_list_prepend (*results, de);
278 return 0;
279 }
280
281 if (!tree2) {
282 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,
283 tree1, basedir);
284 *results = g_list_prepend (*results, de);
285 return 0;
286 }
287
288 if (!dirent_same (tree1, tree2)) {
289 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
290 tree2, basedir);
291 de->origin_size = tree1->size;
292 *results = g_list_prepend (*results, de);
293 }
294
295 return 0;
296 }
297
298 static int
twoway_diff_dirs(int n,const char * basedir,SeafDirent * dirs[],void * vdata,gboolean * recurse)299 twoway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,
300 gboolean *recurse)
301 {
302 DiffData *data = vdata;
303 GList **results = data->results;
304 DiffEntry *de;
305 SeafDirent *tree1 = dirs[0];
306 SeafDirent *tree2 = dirs[1];
307
308 if (!tree1) {
309 if (strcmp (tree2->id, EMPTY_SHA1) == 0 || data->fold_dir_diff) {
310 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED,
311 tree2, basedir);
312 *results = g_list_prepend (*results, de);
313 *recurse = FALSE;
314 } else
315 *recurse = TRUE;
316 return 0;
317 }
318
319 if (!tree2) {
320 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS,
321 DIFF_STATUS_DIR_DELETED,
322 tree1, basedir);
323 *results = g_list_prepend (*results, de);
324
325 if (data->fold_dir_diff) {
326 *recurse = FALSE;
327 } else
328 *recurse = TRUE;
329 return 0;
330 }
331
332 return 0;
333 }
334
335 int
diff_commits(SeafCommit * commit1,SeafCommit * commit2,GList ** results,gboolean fold_dir_diff)336 diff_commits (SeafCommit *commit1, SeafCommit *commit2, GList **results,
337 gboolean fold_dir_diff)
338 {
339 SeafRepo *repo = NULL;
340 DiffOptions opt;
341 const char *roots[2];
342
343 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, commit1->repo_id);
344 if (!repo) {
345 seaf_warning ("Failed to get repo %s.\n", commit1->repo_id);
346 return -1;
347 }
348
349 DiffData data;
350 memset (&data, 0, sizeof(data));
351 data.results = results;
352 data.fold_dir_diff = fold_dir_diff;
353
354 memset (&opt, 0, sizeof(opt));
355 #ifdef SEAFILE_SERVER
356 memcpy (opt.store_id, repo->store_id, 36);
357 #else
358 memcpy (opt.store_id, repo->id, 36);
359 #endif
360 opt.version = repo->version;
361 opt.file_cb = twoway_diff_files;
362 opt.dir_cb = twoway_diff_dirs;
363 opt.data = &data;
364
365 #ifdef SEAFILE_SERVER
366 seaf_repo_unref (repo);
367 #endif
368
369 roots[0] = commit1->root_id;
370 roots[1] = commit2->root_id;
371
372 diff_trees (2, roots, &opt);
373 diff_resolve_renames (results);
374
375 return 0;
376 }
377
378 int
diff_commit_roots(const char * store_id,int version,const char * root1,const char * root2,GList ** results,gboolean fold_dir_diff)379 diff_commit_roots (const char *store_id, int version,
380 const char *root1, const char *root2, GList **results,
381 gboolean fold_dir_diff)
382 {
383 DiffOptions opt;
384 const char *roots[2];
385
386 DiffData data;
387 memset (&data, 0, sizeof(data));
388 data.results = results;
389 data.fold_dir_diff = fold_dir_diff;
390
391 memset (&opt, 0, sizeof(opt));
392 memcpy (opt.store_id, store_id, 36);
393 opt.version = version;
394 opt.file_cb = twoway_diff_files;
395 opt.dir_cb = twoway_diff_dirs;
396 opt.data = &data;
397
398 roots[0] = root1;
399 roots[1] = root2;
400
401 diff_trees (2, roots, &opt);
402 diff_resolve_renames (results);
403
404 return 0;
405 }
406
407 static int
threeway_diff_files(int n,const char * basedir,SeafDirent * files[],void * vdata)408 threeway_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)
409 {
410 DiffData *data = vdata;
411 SeafDirent *m = files[0];
412 SeafDirent *p1 = files[1];
413 SeafDirent *p2 = files[2];
414 GList **results = data->results;
415 DiffEntry *de;
416
417 /* diff m with both p1 and p2. */
418 if (m && p1 && p2) {
419 if (!dirent_same(m, p1) && !dirent_same (m, p2)) {
420 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
421 m, basedir);
422 *results = g_list_prepend (*results, de);
423 }
424 } else if (!m && p1 && p2) {
425 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_DELETED,
426 p1, basedir);
427 *results = g_list_prepend (*results, de);
428 } else if (m && !p1 && p2) {
429 if (!dirent_same (m, p2)) {
430 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
431 m, basedir);
432 *results = g_list_prepend (*results, de);
433 }
434 } else if (m && p1 && !p2) {
435 if (!dirent_same (m, p1)) {
436 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_MODIFIED,
437 m, basedir);
438 *results = g_list_prepend (*results, de);
439 }
440 } else if (m && !p1 && !p2) {
441 de = diff_entry_new_from_dirent (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
442 m, basedir);
443 *results = g_list_prepend (*results, de);
444 }
445 /* Nothing to do for:
446 * 1. !m && p1 && !p2;
447 * 2. !m && !p1 && p2;
448 * 3. !m && !p1 && !p2 (should not happen)
449 */
450
451 return 0;
452 }
453
454 static int
threeway_diff_dirs(int n,const char * basedir,SeafDirent * dirs[],void * vdata,gboolean * recurse)455 threeway_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *vdata,
456 gboolean *recurse)
457 {
458 *recurse = TRUE;
459 return 0;
460 }
461
462 int
diff_merge(SeafCommit * merge,GList ** results,gboolean fold_dir_diff)463 diff_merge (SeafCommit *merge, GList **results, gboolean fold_dir_diff)
464 {
465 SeafRepo *repo = NULL;
466 DiffOptions opt;
467 const char *roots[3];
468 SeafCommit *parent1, *parent2;
469
470 g_return_val_if_fail (*results == NULL, -1);
471 g_return_val_if_fail (merge->parent_id != NULL &&
472 merge->second_parent_id != NULL,
473 -1);
474
475 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, merge->repo_id);
476 if (!repo) {
477 seaf_warning ("Failed to get repo %s.\n", merge->repo_id);
478 return -1;
479 }
480
481 parent1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
482 repo->id,
483 repo->version,
484 merge->parent_id);
485 if (!parent1) {
486 seaf_warning ("failed to find commit %s:%s.\n", repo->id, merge->parent_id);
487 return -1;
488 }
489
490 parent2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
491 repo->id,
492 repo->version,
493 merge->second_parent_id);
494 if (!parent2) {
495 seaf_warning ("failed to find commit %s:%s.\n",
496 repo->id, merge->second_parent_id);
497 seaf_commit_unref (parent1);
498 return -1;
499 }
500
501 DiffData data;
502 memset (&data, 0, sizeof(data));
503 data.results = results;
504 data.fold_dir_diff = fold_dir_diff;
505
506 memset (&opt, 0, sizeof(opt));
507 #ifdef SEAFILE_SERVER
508 memcpy (opt.store_id, repo->store_id, 36);
509 #else
510 memcpy (opt.store_id, repo->id, 36);
511 #endif
512 opt.version = repo->version;
513 opt.file_cb = threeway_diff_files;
514 opt.dir_cb = threeway_diff_dirs;
515 opt.data = &data;
516
517 #ifdef SEAFILE_SERVER
518 seaf_repo_unref (repo);
519 #endif
520
521 roots[0] = merge->root_id;
522 roots[1] = parent1->root_id;
523 roots[2] = parent2->root_id;
524
525 int ret = diff_trees (3, roots, &opt);
526 diff_resolve_renames (results);
527
528 seaf_commit_unref (parent1);
529 seaf_commit_unref (parent2);
530
531 return ret;
532 }
533
534 int
diff_merge_roots(const char * store_id,int version,const char * merged_root,const char * p1_root,const char * p2_root,GList ** results,gboolean fold_dir_diff)535 diff_merge_roots (const char *store_id, int version,
536 const char *merged_root, const char *p1_root, const char *p2_root,
537 GList **results, gboolean fold_dir_diff)
538 {
539 DiffOptions opt;
540 const char *roots[3];
541
542 g_return_val_if_fail (*results == NULL, -1);
543
544 DiffData data;
545 memset (&data, 0, sizeof(data));
546 data.results = results;
547 data.fold_dir_diff = fold_dir_diff;
548
549 memset (&opt, 0, sizeof(opt));
550 memcpy (opt.store_id, store_id, 36);
551 opt.version = version;
552 opt.file_cb = threeway_diff_files;
553 opt.dir_cb = threeway_diff_dirs;
554 opt.data = &data;
555
556 roots[0] = merged_root;
557 roots[1] = p1_root;
558 roots[2] = p2_root;
559
560 diff_trees (3, roots, &opt);
561 diff_resolve_renames (results);
562
563 return 0;
564 }
565
566 /* This function only resolve "strict" rename, i.e. two files must be
567 * exactly the same.
568 * Don't detect rename of empty files and empty dirs.
569 */
570 void
diff_resolve_renames(GList ** diff_entries)571 diff_resolve_renames (GList **diff_entries)
572 {
573 GHashTable *deleted_files = NULL, *deleted_dirs = NULL;
574 GList *p;
575 GList *added = NULL;
576 DiffEntry *de;
577 unsigned char empty_sha1[20];
578 unsigned int deleted_empty_count = 0, deleted_empty_dir_count = 0;
579 unsigned int added_empty_count = 0, added_empty_dir_count = 0;
580 gboolean check_empty_dir, check_empty_file;
581
582 memset (empty_sha1, 0, 20);
583
584 /* Hash and equal functions for raw sha1. */
585 deleted_dirs = g_hash_table_new (ccnet_sha1_hash, ccnet_sha1_equal);
586 deleted_files = g_hash_table_new (ccnet_sha1_hash, ccnet_sha1_equal);
587
588 /* Count deleted and added entries of which content is empty. */
589 for (p = *diff_entries; p != NULL; p = p->next) {
590 de = p->data;
591 if (memcmp (de->sha1, empty_sha1, 20) == 0) {
592 if (de->status == DIFF_STATUS_DELETED)
593 deleted_empty_count++;
594 if (de->status == DIFF_STATUS_DIR_DELETED)
595 deleted_empty_dir_count++;
596 if (de->status == DIFF_STATUS_ADDED)
597 added_empty_count++;
598 if (de->status == DIFF_STATUS_DIR_ADDED)
599 added_empty_dir_count++;
600 }
601 }
602
603 check_empty_dir = (deleted_empty_dir_count == 1 && added_empty_dir_count == 1);
604 check_empty_file = (deleted_empty_count == 1 && added_empty_count == 1);
605
606 /* Collect all "deleted" entries. */
607 for (p = *diff_entries; p != NULL; p = p->next) {
608 de = p->data;
609 if (de->status == DIFF_STATUS_DELETED) {
610 if (memcmp (de->sha1, empty_sha1, 20) == 0 &&
611 check_empty_file == FALSE)
612 continue;
613
614 g_hash_table_insert (deleted_files, de->sha1, p);
615 }
616
617 if (de->status == DIFF_STATUS_DIR_DELETED) {
618 if (memcmp (de->sha1, empty_sha1, 20) == 0 &&
619 check_empty_dir == FALSE)
620 continue;
621
622 g_hash_table_insert (deleted_dirs, de->sha1, p);
623 }
624 }
625
626 /* Collect all "added" entries into a separate list. */
627 for (p = *diff_entries; p != NULL; p = p->next) {
628 de = p->data;
629 if (de->status == DIFF_STATUS_ADDED) {
630 if (memcmp (de->sha1, empty_sha1, 20) == 0 &&
631 check_empty_file == 0)
632 continue;
633
634 added = g_list_prepend (added, p);
635 }
636
637 if (de->status == DIFF_STATUS_DIR_ADDED) {
638 if (memcmp (de->sha1, empty_sha1, 20) == 0 &&
639 check_empty_dir == 0)
640 continue;
641
642 added = g_list_prepend (added, p);
643 }
644 }
645
646 /* For each "added" entry, if we find a "deleted" entry with
647 * the same content, we find a rename pair.
648 */
649 p = added;
650 while (p != NULL) {
651 GList *p_add, *p_del;
652 DiffEntry *de_add, *de_del, *de_rename;
653 int rename_status;
654
655 p_add = p->data;
656 de_add = p_add->data;
657
658 if (de_add->status == DIFF_STATUS_ADDED)
659 p_del = g_hash_table_lookup (deleted_files, de_add->sha1);
660 else
661 p_del = g_hash_table_lookup (deleted_dirs, de_add->sha1);
662
663 if (p_del) {
664 de_del = p_del->data;
665
666 if (de_add->status == DIFF_STATUS_DIR_ADDED)
667 rename_status = DIFF_STATUS_DIR_RENAMED;
668 else
669 rename_status = DIFF_STATUS_RENAMED;
670
671 de_rename = diff_entry_new (de_del->type, rename_status,
672 de_del->sha1, de_del->name);
673 de_rename->new_name = g_strdup(de_add->name);
674
675 *diff_entries = g_list_delete_link (*diff_entries, p_add);
676 *diff_entries = g_list_delete_link (*diff_entries, p_del);
677 *diff_entries = g_list_prepend (*diff_entries, de_rename);
678
679 if (de_del->status == DIFF_STATUS_DIR_DELETED)
680 g_hash_table_remove (deleted_dirs, de_add->sha1);
681 else
682 g_hash_table_remove (deleted_files, de_add->sha1);
683
684 diff_entry_free (de_add);
685 diff_entry_free (de_del);
686 }
687
688 p = g_list_delete_link (p, p);
689 }
690
691 g_hash_table_destroy (deleted_dirs);
692 g_hash_table_destroy (deleted_files);
693 }
694
695 static gboolean
is_redundant_empty_dir(DiffEntry * de_dir,DiffEntry * de_file)696 is_redundant_empty_dir (DiffEntry *de_dir, DiffEntry *de_file)
697 {
698 int dir_len;
699
700 if (de_dir->status == DIFF_STATUS_DIR_ADDED &&
701 de_file->status == DIFF_STATUS_DELETED)
702 {
703 dir_len = strlen (de_dir->name);
704 if (strlen (de_file->name) > dir_len &&
705 strncmp (de_dir->name, de_file->name, dir_len) == 0)
706 return TRUE;
707 }
708
709 if (de_dir->status == DIFF_STATUS_DIR_DELETED &&
710 de_file->status == DIFF_STATUS_ADDED)
711 {
712 dir_len = strlen (de_dir->name);
713 if (strlen (de_file->name) > dir_len &&
714 strncmp (de_dir->name, de_file->name, dir_len) == 0)
715 return TRUE;
716 }
717
718 return FALSE;
719 }
720
721 /*
722 * An empty dir entry may be added by deleting all the files under it.
723 * Similarly, an empty dir entry may be deleted by adding some file in it.
724 * In both cases, we don't want to include the empty dir entry in the
725 * diff results.
726 */
727 void
diff_resolve_empty_dirs(GList ** diff_entries)728 diff_resolve_empty_dirs (GList **diff_entries)
729 {
730 GList *empty_dirs = NULL;
731 GList *p, *dir, *file;
732 DiffEntry *de, *de_dir, *de_file;
733
734 for (p = *diff_entries; p != NULL; p = p->next) {
735 de = p->data;
736 if (de->status == DIFF_STATUS_DIR_ADDED ||
737 de->status == DIFF_STATUS_DIR_DELETED)
738 empty_dirs = g_list_prepend (empty_dirs, p);
739 }
740
741 for (dir = empty_dirs; dir != NULL; dir = dir->next) {
742 de_dir = ((GList *)dir->data)->data;
743 for (file = *diff_entries; file != NULL; file = file->next) {
744 de_file = file->data;
745 if (is_redundant_empty_dir (de_dir, de_file)) {
746 *diff_entries = g_list_delete_link (*diff_entries, dir->data);
747 break;
748 }
749 }
750 }
751
752 g_list_free (empty_dirs);
753 }
754
diff_unmerged_state(int mask)755 int diff_unmerged_state(int mask)
756 {
757 mask >>= 1;
758 switch (mask) {
759 case 7:
760 return STATUS_UNMERGED_BOTH_CHANGED;
761 case 3:
762 return STATUS_UNMERGED_OTHERS_REMOVED;
763 case 5:
764 return STATUS_UNMERGED_I_REMOVED;
765 case 6:
766 return STATUS_UNMERGED_BOTH_ADDED;
767 case 2:
768 return STATUS_UNMERGED_DFC_I_ADDED_FILE;
769 case 4:
770 return STATUS_UNMERGED_DFC_OTHERS_ADDED_FILE;
771 default:
772 seaf_warning ("Unexpected unmerged case\n");
773 }
774 return 0;
775 }
776
777 char *
format_diff_results(GList * results)778 format_diff_results(GList *results)
779 {
780 GList *ptr;
781 GString *fmt_status;
782 DiffEntry *de;
783
784 fmt_status = g_string_new("");
785
786 for (ptr = results; ptr; ptr = ptr->next) {
787 de = ptr->data;
788
789 if (de->status != DIFF_STATUS_RENAMED)
790 g_string_append_printf(fmt_status, "%c %c %d %u %s\n",
791 de->type, de->status, de->unmerge_state,
792 (int)strlen(de->name), de->name);
793 else
794 g_string_append_printf(fmt_status, "%c %c %d %u %s %u %s\n",
795 de->type, de->status, de->unmerge_state,
796 (int)strlen(de->name), de->name,
797 (int)strlen(de->new_name), de->new_name);
798 }
799
800 return g_string_free(fmt_status, FALSE);
801 }
802
803 inline static char *
get_basename(char * path)804 get_basename (char *path)
805 {
806 char *slash;
807 slash = strrchr (path, '/');
808 if (!slash)
809 return path;
810 return (slash + 1);
811 }
812
813 char *
diff_results_to_description(GList * results)814 diff_results_to_description (GList *results)
815 {
816 GList *p;
817 DiffEntry *de;
818 char *add_mod_file = NULL, *removed_file = NULL;
819 char *renamed_file = NULL;
820 char *new_dir = NULL, *removed_dir = NULL;
821 int n_add_mod = 0, n_removed = 0, n_renamed = 0;
822 int n_new_dir = 0, n_removed_dir = 0;
823 GString *desc;
824
825 if (results == NULL)
826 return NULL;
827
828 for (p = results; p != NULL; p = p->next) {
829 de = p->data;
830 switch (de->status) {
831 case DIFF_STATUS_ADDED:
832 if (n_add_mod == 0)
833 add_mod_file = get_basename(de->name);
834 n_add_mod++;
835 break;
836 case DIFF_STATUS_DELETED:
837 if (n_removed == 0)
838 removed_file = get_basename(de->name);
839 n_removed++;
840 break;
841 case DIFF_STATUS_RENAMED:
842 if (n_renamed == 0)
843 renamed_file = get_basename(de->name);
844 n_renamed++;
845 break;
846 case DIFF_STATUS_MODIFIED:
847 if (n_add_mod == 0)
848 add_mod_file = get_basename(de->name);
849 n_add_mod++;
850 break;
851 case DIFF_STATUS_DIR_ADDED:
852 if (n_new_dir == 0)
853 new_dir = get_basename(de->name);
854 n_new_dir++;
855 break;
856 case DIFF_STATUS_DIR_DELETED:
857 if (n_removed_dir == 0)
858 removed_dir = get_basename(de->name);
859 n_removed_dir++;
860 break;
861 }
862 }
863
864 desc = g_string_new ("");
865
866 if (n_add_mod == 1)
867 g_string_append_printf (desc, "Added or modified \"%s\".\n", add_mod_file);
868 else if (n_add_mod > 1)
869 g_string_append_printf (desc, "Added or modified \"%s\" and %d more files.\n",
870 add_mod_file, n_add_mod - 1);
871
872 if (n_removed == 1)
873 g_string_append_printf (desc, "Deleted \"%s\".\n", removed_file);
874 else if (n_removed > 1)
875 g_string_append_printf (desc, "Deleted \"%s\" and %d more files.\n",
876 removed_file, n_removed - 1);
877
878 if (n_renamed == 1)
879 g_string_append_printf (desc, "Renamed \"%s\".\n", renamed_file);
880 else if (n_renamed > 1)
881 g_string_append_printf (desc, "Renamed \"%s\" and %d more files.\n",
882 renamed_file, n_renamed - 1);
883
884 if (n_new_dir == 1)
885 g_string_append_printf (desc, "Added directory \"%s\".\n", new_dir);
886 else if (n_new_dir > 1)
887 g_string_append_printf (desc, "Added \"%s\" and %d more directories.\n",
888 new_dir, n_new_dir - 1);
889
890 if (n_removed_dir == 1)
891 g_string_append_printf (desc, "Removed directory \"%s\".\n", removed_dir);
892 else if (n_removed_dir > 1)
893 g_string_append_printf (desc, "Removed \"%s\" and %d more directories.\n",
894 removed_dir, n_removed_dir - 1);
895
896 return g_string_free (desc, FALSE);
897 }
898