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