~ubuntu-branches/ubuntu/vivid/cvsps/vivid

« back to all changes in this revision

Viewing changes to .pc/01_ignoretrunk.patch/cvsps.c

  • Committer: Bazaar Package Importer
  • Author(s): Frank Lichtenheld
  • Date: 2011-04-07 23:18:19 UTC
  • mfrom: (6.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20110407231819-h0yjygw1s7s350dj
Tags: 2.1-6
* QA upload.
* Change maintainer to QA Group
* Simplify packaging by converting to 3.0 (quilt) format
* Add watch file

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
 
3
 * See COPYING file for license information 
 
4
 */
 
5
 
 
6
#include <stdio.h>
 
7
#include <stdlib.h>
 
8
#include <string.h>
 
9
#include <limits.h>
 
10
#include <unistd.h>
 
11
#include <search.h>
 
12
#include <time.h>
 
13
#include <ctype.h>
 
14
#include <sys/stat.h>
 
15
#include <sys/types.h>
 
16
#include <fcntl.h>
 
17
#include <regex.h>
 
18
#include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
 
19
 
 
20
#include <cbtcommon/hash.h>
 
21
#include <cbtcommon/list.h>
 
22
#include <cbtcommon/text_util.h>
 
23
#include <cbtcommon/debug.h>
 
24
#include <cbtcommon/rcsid.h>
 
25
 
 
26
#include "cache.h"
 
27
#include "cvsps_types.h"
 
28
#include "cvsps.h"
 
29
#include "util.h"
 
30
#include "stats.h"
 
31
#include "cap.h"
 
32
#include "cvs_direct.h"
 
33
#include "list_sort.h"
 
34
 
 
35
RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
 
36
 
 
37
#define CVS_LOG_BOUNDARY "----------------------------\n"
 
38
#define CVS_FILE_BOUNDARY "=============================================================================\n"
 
39
 
 
40
enum
 
41
{
 
42
    NEED_FILE,
 
43
    NEED_SYMS,
 
44
    NEED_EOS,
 
45
    NEED_START_LOG,
 
46
    NEED_REVISION,
 
47
    NEED_DATE_AUTHOR_STATE,
 
48
    NEED_EOM
 
49
};
 
50
 
 
51
/* true globals */
 
52
struct hash_table * file_hash;
 
53
CvsServerCtx * cvs_direct_ctx;
 
54
char root_path[PATH_MAX];
 
55
char repository_path[PATH_MAX];
 
56
 
 
57
const char * tag_flag_descr[] = {
 
58
    "",
 
59
    "**FUNKY**",
 
60
    "**INVALID**",
 
61
    "**INVALID**"
 
62
};
 
63
 
 
64
const char * fnk_descr[] = {
 
65
    "",
 
66
    "FNK_SHOW_SOME",
 
67
    "FNK_SHOW_ALL",
 
68
    "FNK_HIDE_ALL",
 
69
    "FNK_HIDE_SOME"
 
70
};
 
71
 
 
72
/* static globals */
 
73
static int ps_counter;
 
74
static void * ps_tree;
 
75
static struct hash_table * global_symbols;
 
76
static char strip_path[PATH_MAX];
 
77
static int strip_path_len;
 
78
static time_t cache_date;
 
79
static int update_cache;
 
80
static int ignore_cache;
 
81
static int do_write_cache;
 
82
static int statistics;
 
83
static const char * test_log_file;
 
84
static struct hash_table * branch_heads;
 
85
static struct list_head all_patch_sets;
 
86
static struct list_head collisions;
 
87
 
 
88
/* settable via options */
 
89
static int timestamp_fuzz_factor = 300;
 
90
static int do_diff;
 
91
static const char * restrict_author;
 
92
static int have_restrict_log;
 
93
static regex_t restrict_log;
 
94
static int have_restrict_file;
 
95
static regex_t restrict_file;
 
96
static time_t restrict_date_start;
 
97
static time_t restrict_date_end;
 
98
static const char * restrict_branch;
 
99
static struct list_head show_patch_set_ranges;
 
100
static int summary_first;
 
101
static const char * norc = "";
 
102
static const char * patch_set_dir;
 
103
static const char * restrict_tag_start;
 
104
static const char * restrict_tag_end;
 
105
static int restrict_tag_ps_start;
 
106
static int restrict_tag_ps_end = INT_MAX;
 
107
static const char * diff_opts;
 
108
static int bkcvs;
 
109
static int no_rlog;
 
110
static int cvs_direct;
 
111
static int compress;
 
112
static char compress_arg[8];
 
113
static int track_branch_ancestry;
 
114
 
 
115
static void check_norc(int, char *[]);
 
116
static int parse_args(int, char *[]);
 
117
static int parse_rc();
 
118
static void load_from_cvs();
 
119
static void init_paths();
 
120
static CvsFile * parse_file(const char *);
 
121
static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
 
122
static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
 
123
static void check_print_patch_set(PatchSet *);
 
124
static void print_patch_set(PatchSet *);
 
125
static void assign_patchset_id(PatchSet *);
 
126
static int compare_rev_strings(const char *, const char *);
 
127
static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
 
128
static int compare_patch_sets_bk(const void *, const void *);
 
129
static int compare_patch_sets(const void *, const void *);
 
130
static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
 
131
static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
 
132
static int is_revision_metadata(const char *);
 
133
static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
 
134
static int patch_set_affects_branch(PatchSet *, const char *);
 
135
static void do_cvs_diff(PatchSet *);
 
136
static PatchSet * create_patch_set();
 
137
static PatchSetRange * create_patch_set_range();
 
138
static void parse_sym(CvsFile *, char *);
 
139
static void resolve_global_symbols();
 
140
static int revision_affects_branch(CvsFileRevision *, const char *);
 
141
static int is_vendor_branch(const char *);
 
142
static void set_psm_initial(PatchSetMember * psm);
 
143
static int check_rev_funk(PatchSet *, CvsFileRevision *);
 
144
static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
 
145
static int before_tag(CvsFileRevision * rev, const char * tag);
 
146
static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
 
147
static void handle_collisions();
 
148
 
 
149
int main(int argc, char *argv[])
 
150
{
 
151
    debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
 
152
 
 
153
    INIT_LIST_HEAD(&show_patch_set_ranges);
 
154
 
 
155
    /*
 
156
     * we want to parse the rc first, so command line can override it
 
157
     * but also, --norc should stop the rc from being processed, so
 
158
     * we look for --norc explicitly first.  Note: --norc in the rc 
 
159
     * file itself will prevent the cvs rc file from being used.
 
160
     */
 
161
    check_norc(argc, argv);
 
162
 
 
163
    if (strlen(norc) == 0 && parse_rc() < 0)
 
164
        exit(1);
 
165
 
 
166
    if (parse_args(argc, argv) < 0)
 
167
        exit(1);
 
168
 
 
169
    if (diff_opts && !cvs_direct && do_diff)
 
170
    {
 
171
        debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
 
172
        debug(DEBUG_APPMSG1, "         which is usually used to create diffs.  'cvs diff'");
 
173
        debug(DEBUG_APPMSG1, "         will be used instead, but the resulting patches ");
 
174
        debug(DEBUG_APPMSG1, "         will need to be applied using the '-p0' option");
 
175
        debug(DEBUG_APPMSG1, "         to patch(1) (in the working directory), ");
 
176
        debug(DEBUG_APPMSG1, "         instead of '-p1'\n");
 
177
    }
 
178
 
 
179
    file_hash = create_hash_table(1023);
 
180
    global_symbols = create_hash_table(111);
 
181
    branch_heads = create_hash_table(1023);
 
182
    INIT_LIST_HEAD(&all_patch_sets);
 
183
    INIT_LIST_HEAD(&collisions);
 
184
 
 
185
    /* this parses some of the CVS/ files, and initializes
 
186
     * the repository_path and other variables 
 
187
     */
 
188
    init_paths();
 
189
 
 
190
    if (!ignore_cache)
 
191
    {
 
192
        int save_fuzz_factor = timestamp_fuzz_factor;
 
193
 
 
194
        /* the timestamp fuzz should only be in effect when loading from
 
195
         * CVS, not re-fuzzed when loading from cache.  This is a hack
 
196
         * working around bad use of global variables
 
197
         */
 
198
 
 
199
        timestamp_fuzz_factor = 0;
 
200
 
 
201
        if ((cache_date = read_cache()) < 0)
 
202
            update_cache = 1;
 
203
 
 
204
        timestamp_fuzz_factor = save_fuzz_factor;
 
205
    }
 
206
 
 
207
    if (cvs_direct && (do_diff || (update_cache && !test_log_file)))
 
208
        cvs_direct_ctx = open_cvs_server(root_path, compress);
 
209
 
 
210
    if (update_cache)
 
211
    {
 
212
        load_from_cvs();
 
213
        do_write_cache = 1;
 
214
    }
 
215
 
 
216
    //XXX
 
217
    //handle_collisions();
 
218
 
 
219
    list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
 
220
 
 
221
    ps_counter = 0;
 
222
    walk_all_patch_sets(assign_patchset_id);
 
223
 
 
224
    handle_collisions();
 
225
 
 
226
    resolve_global_symbols();
 
227
 
 
228
    if (do_write_cache)
 
229
        write_cache(cache_date);
 
230
 
 
231
    if (statistics)
 
232
        print_statistics(ps_tree);
 
233
 
 
234
    /* check that the '-r' symbols (if specified) were resolved */
 
235
    if (restrict_tag_start && restrict_tag_ps_start == 0 && 
 
236
        strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
 
237
    {
 
238
        debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
 
239
        exit(1);
 
240
    }
 
241
 
 
242
    if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
 
243
    {
 
244
        debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
 
245
        exit(1);
 
246
    }
 
247
 
 
248
    walk_all_patch_sets(check_print_patch_set);
 
249
 
 
250
    if (summary_first++)
 
251
        walk_all_patch_sets(check_print_patch_set);
 
252
 
 
253
    if (cvs_direct_ctx)
 
254
        close_cvs_server(cvs_direct_ctx);
 
255
 
 
256
    exit(0);
 
257
}
 
258
 
 
259
static void load_from_cvs()
 
260
{
 
261
    FILE * cvsfp;
 
262
    char buff[BUFSIZ];
 
263
    int state = NEED_FILE;
 
264
    CvsFile * file = NULL;
 
265
    PatchSetMember * psm = NULL;
 
266
    char datebuff[20];
 
267
    char authbuff[AUTH_STR_MAX];
 
268
    char logbuff[LOG_STR_MAX + 1];
 
269
    int loglen = 0;
 
270
    int have_log = 0;
 
271
    char cmd[BUFSIZ];
 
272
    char date_str[64];
 
273
    char use_rep_buff[PATH_MAX];
 
274
    char * ltype;
 
275
 
 
276
    if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
 
277
    {
 
278
        ltype = "rlog";
 
279
        snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
 
280
    }
 
281
    else
 
282
    {
 
283
        ltype = "log";
 
284
        use_rep_buff[0] = 0;
 
285
    }
 
286
 
 
287
    if (cache_date > 0)
 
288
    {
 
289
        struct tm * tm = gmtime(&cache_date);
 
290
        strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
 
291
 
 
292
        /* this command asks for logs using two different date
 
293
         * arguments, separated by ';' (see man rlog).  The first
 
294
         * gets all revisions more recent than date, the second 
 
295
         * gets a single revision no later than date, which combined
 
296
         * get us all revisions that have occurred since last update
 
297
         * and overlaps what we had before by exactly one revision,
 
298
         * which is necessary to fill in the pre_rev stuff for a 
 
299
         * PatchSetMember
 
300
         */
 
301
        snprintf(cmd, BUFSIZ, "cvs %s %s %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
 
302
    }
 
303
    else
 
304
    {
 
305
        date_str[0] = 0;
 
306
        snprintf(cmd, BUFSIZ, "cvs %s %s %s %s", compress_arg, norc, ltype, use_rep_buff);
 
307
    }
 
308
    
 
309
    debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
 
310
 
 
311
    cache_date = time(NULL);
 
312
 
 
313
    /* FIXME: this is ugly, need to virtualize the accesses away from here */
 
314
    if (test_log_file)
 
315
        cvsfp = fopen(test_log_file, "r");
 
316
    else if (cvs_direct_ctx)
 
317
        cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
 
318
    else
 
319
        cvsfp = popen(cmd, "r");
 
320
 
 
321
    if (!cvsfp)
 
322
    {
 
323
        debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
 
324
        exit(1);
 
325
    }
 
326
 
 
327
    for (;;)
 
328
    {
 
329
        char * tst;
 
330
        if (cvs_direct_ctx)
 
331
            tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
 
332
        else
 
333
            tst = fgets(buff, BUFSIZ, cvsfp);
 
334
 
 
335
        if (!tst)
 
336
            break;
 
337
 
 
338
        debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
 
339
 
 
340
        switch(state)
 
341
        {
 
342
        case NEED_FILE:
 
343
            if (strncmp(buff, "RCS file", 8) == 0 && (file = parse_file(buff)))
 
344
                state = NEED_SYMS;
 
345
            break;
 
346
        case NEED_SYMS:
 
347
            if (strncmp(buff, "symbolic names:", 15) == 0)
 
348
                state = NEED_EOS;
 
349
            break;
 
350
        case NEED_EOS:
 
351
            if (!isspace(buff[0]))
 
352
            {
 
353
                /* see cvsps_types.h for commentary on have_branches */
 
354
                file->have_branches = 1;
 
355
                state = NEED_START_LOG;
 
356
            }
 
357
            else
 
358
                parse_sym(file, buff);
 
359
            break;
 
360
        case NEED_START_LOG:
 
361
            if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
 
362
                state = NEED_REVISION;
 
363
            break;
 
364
        case NEED_REVISION:
 
365
            if (strncmp(buff, "revision", 8) == 0)
 
366
            {
 
367
                char new_rev[REV_STR_MAX];
 
368
                CvsFileRevision * rev;
 
369
 
 
370
                strcpy(new_rev, buff + 9);
 
371
                chop(new_rev);
 
372
 
 
373
                /* 
 
374
                 * rev may already exist (think cvsps -u), in which
 
375
                 * case parse_revision is a hash lookup
 
376
                 */
 
377
                rev = parse_revision(file, new_rev);
 
378
 
 
379
                /* 
 
380
                 * in the simple case, we are copying rev to psm->pre_rev
 
381
                 * (psm refers to last patch set processed at this point)
 
382
                 * since generally speaking the log is reverse chronological.
 
383
                 * This breaks down slightly when branches are introduced 
 
384
                 */
 
385
 
 
386
                assign_pre_revision(psm, rev);
 
387
 
 
388
                /*
 
389
                 * if this is a new revision, it will have no post_psm associated.
 
390
                 * otherwise we are (probably?) hitting the overlap in cvsps -u 
 
391
                 */
 
392
                if (!rev->post_psm)
 
393
                {
 
394
                    psm = rev->post_psm = create_patch_set_member();
 
395
                    psm->post_rev = rev;
 
396
                    psm->file = file;
 
397
                    state = NEED_DATE_AUTHOR_STATE;
 
398
                }
 
399
                else
 
400
                {
 
401
                    /* we hit this in cvsps -u mode, we are now up-to-date
 
402
                     * w.r.t this particular file. skip all of the rest 
 
403
                     * of the info (revs and logs) until we hit the next file
 
404
                     */
 
405
                    psm = NULL;
 
406
                    state = NEED_EOM;
 
407
                }
 
408
            }
 
409
            break;
 
410
        case NEED_DATE_AUTHOR_STATE:
 
411
            if (strncmp(buff, "date:", 5) == 0)
 
412
            {
 
413
                char * p;
 
414
 
 
415
                strncpy(datebuff, buff + 6, 19);
 
416
                datebuff[19] = 0;
 
417
 
 
418
                strcpy(authbuff, "unknown");
 
419
                p = strstr(buff, "author: ");
 
420
                if (p)
 
421
                {
 
422
                    char * op;
 
423
                    p += 8;
 
424
                    op = strchr(p, ';');
 
425
                    if (op)
 
426
                    {
 
427
                        strzncpy(authbuff, p, op - p + 1);
 
428
                    }
 
429
                }
 
430
                
 
431
                /* read the 'state' tag to see if this is a dead revision */
 
432
                p = strstr(buff, "state: ");
 
433
                if (p)
 
434
                {
 
435
                    char * op;
 
436
                    p += 7;
 
437
                    op = strchr(p, ';');
 
438
                    if (op)
 
439
                        if (strncmp(p, "dead", MIN(4, op - p)) == 0)
 
440
                            psm->post_rev->dead = 1;
 
441
                }
 
442
 
 
443
                state = NEED_EOM;
 
444
            }
 
445
            break;
 
446
        case NEED_EOM:
 
447
            if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
 
448
            {
 
449
                if (psm)
 
450
                {
 
451
                    PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
 
452
                    patch_set_add_member(ps, psm);
 
453
                }
 
454
 
 
455
                logbuff[0] = 0;
 
456
                loglen = 0;
 
457
                have_log = 0;
 
458
                state = NEED_REVISION;
 
459
            }
 
460
            else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
 
461
            {
 
462
                if (psm)
 
463
                {
 
464
                    PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
 
465
                    patch_set_add_member(ps, psm);
 
466
                    assign_pre_revision(psm, NULL);
 
467
                }
 
468
 
 
469
                logbuff[0] = 0;
 
470
                loglen = 0;
 
471
                have_log = 0;
 
472
                psm = NULL;
 
473
                file = NULL;
 
474
                state = NEED_FILE;
 
475
            }
 
476
            else
 
477
            {
 
478
                /* other "blahblah: information;" messages can 
 
479
                 * follow the stuff we pay attention to
 
480
                 */
 
481
                if (have_log || !is_revision_metadata(buff))
 
482
                {
 
483
                    /* if the log buffer is full, that's it.  
 
484
                     * 
 
485
                     * Also, read lines (fgets) always have \n in them
 
486
                     * which we count on.  So if truncation happens,
 
487
                     * be careful to put a \n on.
 
488
                     * 
 
489
                     * Buffer has LOG_STR_MAX + 1 for room for \0 if
 
490
                     * necessary
 
491
                     */
 
492
                    if (loglen < LOG_STR_MAX)
 
493
                    {
 
494
                        int len = strlen(buff);
 
495
                        
 
496
                        if (len >= LOG_STR_MAX - loglen)
 
497
                        {
 
498
                            debug(DEBUG_APPMSG1, "WARNING: maximum log length exceeded, truncating log");
 
499
                            len = LOG_STR_MAX - loglen;
 
500
                            buff[len - 1] = '\n';
 
501
                        }
 
502
 
 
503
                        debug(DEBUG_STATUS, "appending %s to log", buff);
 
504
                        memcpy(logbuff + loglen, buff, len);
 
505
                        loglen += len;
 
506
                        logbuff[loglen] = 0;
 
507
                        have_log = 1;
 
508
                    }
 
509
                }
 
510
                else 
 
511
                {
 
512
                    debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
 
513
                }
 
514
            }
 
515
 
 
516
            break;
 
517
        }
 
518
    }
 
519
 
 
520
    if (state == NEED_SYMS)
 
521
    {
 
522
        debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
 
523
        debug(DEBUG_APPERROR, "       Perhaps you should try running with --norc");
 
524
        exit(1);
 
525
    }
 
526
 
 
527
    if (state != NEED_FILE)
 
528
    {
 
529
        debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d)  Use -v to debug", state);
 
530
        exit(1);
 
531
    }
 
532
    
 
533
    if (test_log_file)
 
534
    {
 
535
        fclose(cvsfp);
 
536
    }
 
537
    else if (cvs_direct_ctx)
 
538
    {
 
539
        cvs_rlog_close(cvs_direct_ctx);
 
540
    }
 
541
    else
 
542
    {
 
543
        if (pclose(cvsfp) < 0)
 
544
        {
 
545
            debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
 
546
            exit(1);
 
547
        }
 
548
    }
 
549
}
 
550
 
 
551
static int usage(const char * str1, const char * str2)
 
552
{
 
553
    if (str1)
 
554
        debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
 
555
 
 
556
    debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]]  ");
 
557
    debug(DEBUG_APPERROR, "             [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
 
558
    debug(DEBUG_APPERROR, "             [-b <branch>]  [-l <regex>] [-r <tag> [-r <tag>]] ");
 
559
    debug(DEBUG_APPERROR, "             [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
 
560
    debug(DEBUG_APPERROR, "             [--test-log <captured cvs log file>] [--bkcvs]");
 
561
    debug(DEBUG_APPERROR, "             [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
 
562
    debug(DEBUG_APPERROR, "             [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
 
563
    debug(DEBUG_APPERROR, "             [-q] [-A] [<repository>]");
 
564
    debug(DEBUG_APPERROR, "");
 
565
    debug(DEBUG_APPERROR, "Where:");
 
566
    debug(DEBUG_APPERROR, "  -h display this informative message");
 
567
    debug(DEBUG_APPERROR, "  -x ignore (and rebuild) cvsps.cache file");
 
568
    debug(DEBUG_APPERROR, "  -u update cvsps.cache file");
 
569
    debug(DEBUG_APPERROR, "  -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
 
570
    debug(DEBUG_APPERROR, "  -g generate diffs of the selected patch sets");
 
571
    debug(DEBUG_APPERROR, "  -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
 
572
    debug(DEBUG_APPERROR, "  -a <author> restrict output to patch sets created by author");
 
573
    debug(DEBUG_APPERROR, "  -f <file> restrict output to patch sets involving file");
 
574
    debug(DEBUG_APPERROR, "  -d <date1> -d <date2> if just one date specified, show");
 
575
    debug(DEBUG_APPERROR, "     revisions newer than date1.  If two dates specified,");
 
576
    debug(DEBUG_APPERROR, "     show revisions between two dates.");
 
577
    debug(DEBUG_APPERROR, "  -b <branch> restrict output to patch sets affecting history of branch");
 
578
    debug(DEBUG_APPERROR, "  -l <regex> restrict output to patch sets matching <regex> in log message");
 
579
    debug(DEBUG_APPERROR, "  -r <tag1> -r <tag2> if just one tag specified, show");
 
580
    debug(DEBUG_APPERROR, "     revisions since tag1. If two tags specified, show");
 
581
    debug(DEBUG_APPERROR, "     revisions between the two tags.");
 
582
    debug(DEBUG_APPERROR, "  -p <directory> output patch sets to individual files in <directory>");
 
583
    debug(DEBUG_APPERROR, "  -v show very verbose parsing messages");
 
584
    debug(DEBUG_APPERROR, "  -t show some brief memory usage statistics");
 
585
    debug(DEBUG_APPERROR, "  --norc when invoking cvs, ignore the .cvsrc file");
 
586
    debug(DEBUG_APPERROR, "  --summary-first when multiple patch sets are shown, put all summaries first");
 
587
    debug(DEBUG_APPERROR, "  --test-log <captured cvs log> supply a captured cvs log for testing");
 
588
    debug(DEBUG_APPERROR, "  --diff-opts <option string> supply special set of options to diff");
 
589
    debug(DEBUG_APPERROR, "  --bkcvs special hack for parsing the BK -> CVS log format");
 
590
    debug(DEBUG_APPERROR, "  --no-rlog disable rlog (it's faulty in some setups)");
 
591
    debug(DEBUG_APPERROR, "  --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
 
592
    debug(DEBUG_APPERROR, "  --debuglvl <bitmask> enable various debug channels.");
 
593
    debug(DEBUG_APPERROR, "  -Z <compression> A value 1-9 which specifies amount of compression");
 
594
    debug(DEBUG_APPERROR, "  --root <cvsroot> specify cvsroot.  overrides env. and working directory (cvs-direct only)");
 
595
    debug(DEBUG_APPERROR, "  -q be quiet about warnings");
 
596
    debug(DEBUG_APPERROR, "  -A track and report branch ancestry");
 
597
    debug(DEBUG_APPERROR, "  <repository> apply cvsps to repository.  overrides working directory");
 
598
    debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
 
599
 
 
600
    return -1;
 
601
}
 
602
 
 
603
static int parse_args(int argc, char *argv[])
 
604
{
 
605
    int i = 1;
 
606
    while (i < argc)
 
607
    {
 
608
        if (strcmp(argv[i], "-z") == 0)
 
609
        {
 
610
            if (++i >= argc)
 
611
                return usage("argument to -z missing", "");
 
612
 
 
613
            timestamp_fuzz_factor = atoi(argv[i++]);
 
614
            continue;
 
615
        }
 
616
        
 
617
        if (strcmp(argv[i], "-g") == 0)
 
618
        {
 
619
            do_diff = 1;
 
620
            i++;
 
621
            continue;
 
622
        }
 
623
        
 
624
        if (strcmp(argv[i], "-s") == 0)
 
625
        {
 
626
            PatchSetRange * range;
 
627
            char * min_str, * max_str;
 
628
 
 
629
            if (++i >= argc)
 
630
                return usage("argument to -s missing", "");
 
631
 
 
632
            min_str = strtok(argv[i++], ",");
 
633
            do
 
634
            {
 
635
                range = create_patch_set_range();
 
636
 
 
637
                max_str = strrchr(min_str, '-');
 
638
                if (max_str)
 
639
                    *max_str++ = '\0';
 
640
                else
 
641
                    max_str = min_str;
 
642
 
 
643
                range->min_counter = atoi(min_str);
 
644
 
 
645
                if (*max_str)
 
646
                    range->max_counter = atoi(max_str);
 
647
                else
 
648
                    range->max_counter = INT_MAX;
 
649
 
 
650
                list_add(&range->link, show_patch_set_ranges.prev);
 
651
            }
 
652
            while ((min_str = strtok(NULL, ",")));
 
653
 
 
654
            continue;
 
655
        }
 
656
        
 
657
        if (strcmp(argv[i], "-a") == 0)
 
658
        {
 
659
            if (++i >= argc)
 
660
                return usage("argument to -a missing", "");
 
661
 
 
662
            restrict_author = argv[i++];
 
663
            continue;
 
664
        }
 
665
 
 
666
        if (strcmp(argv[i], "-l") == 0)
 
667
        {
 
668
            int err;
 
669
 
 
670
            if (++i >= argc)
 
671
                return usage("argument to -l missing", "");
 
672
 
 
673
            if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
 
674
            {
 
675
                char errbuf[256];
 
676
                regerror(err, &restrict_log, errbuf, 256);
 
677
                return usage("bad regex to -l", errbuf);
 
678
            }
 
679
 
 
680
            have_restrict_log = 1;
 
681
 
 
682
            continue;
 
683
        }
 
684
 
 
685
        if (strcmp(argv[i], "-f") == 0)
 
686
        {
 
687
            int err;
 
688
 
 
689
            if (++i >= argc)
 
690
                return usage("argument to -f missing", "");
 
691
 
 
692
            if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
 
693
            {
 
694
                char errbuf[256];
 
695
                regerror(err, &restrict_file, errbuf, 256);
 
696
                return usage("bad regex to -f", errbuf);
 
697
            }
 
698
 
 
699
            have_restrict_file = 1;
 
700
 
 
701
            continue;
 
702
        }
 
703
        
 
704
        if (strcmp(argv[i], "-d") == 0)
 
705
        {
 
706
            time_t *pt;
 
707
 
 
708
            if (++i >= argc)
 
709
                return usage("argument to -d missing", "");
 
710
 
 
711
            pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
 
712
            convert_date(pt, argv[i++]);
 
713
            continue;
 
714
        }
 
715
 
 
716
        if (strcmp(argv[i], "-r") == 0)
 
717
        {
 
718
            if (++i >= argc)
 
719
                return usage("argument to -r missing", "");
 
720
 
 
721
            if (restrict_tag_start)
 
722
                restrict_tag_end = argv[i];
 
723
            else
 
724
                restrict_tag_start = argv[i];
 
725
 
 
726
            i++;
 
727
            continue;
 
728
        }
 
729
 
 
730
        if (strcmp(argv[i], "-u") == 0)
 
731
        {
 
732
            update_cache = 1;
 
733
            i++;
 
734
            continue;
 
735
        }
 
736
        
 
737
        if (strcmp(argv[i], "-x") == 0)
 
738
        {
 
739
            ignore_cache = 1;
 
740
            update_cache = 1;
 
741
            i++;
 
742
            continue;
 
743
        }
 
744
 
 
745
        if (strcmp(argv[i], "-b") == 0)
 
746
        {
 
747
            if (++i >= argc)
 
748
                return usage("argument to -b missing", "");
 
749
 
 
750
            restrict_branch = argv[i++];
 
751
            /* Warn if the user tries to use TRUNK. Should eventually
 
752
             * go away as TRUNK may be a valid branch within CVS
 
753
             */
 
754
            if (strcmp(restrict_branch, "TRUNK") == 0)
 
755
                debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
 
756
            continue;
 
757
        }
 
758
 
 
759
        if (strcmp(argv[i], "-p") == 0)
 
760
        {
 
761
            if (++i >= argc)
 
762
                return usage("argument to -p missing", "");
 
763
            
 
764
            patch_set_dir = argv[i++];
 
765
            continue;
 
766
        }
 
767
 
 
768
        if (strcmp(argv[i], "-v") == 0)
 
769
        {
 
770
            debuglvl = ~0;
 
771
            i++;
 
772
            continue;
 
773
        }
 
774
        
 
775
        if (strcmp(argv[i], "-t") == 0)
 
776
        {
 
777
            statistics = 1;
 
778
            i++;
 
779
            continue;
 
780
        }
 
781
 
 
782
        if (strcmp(argv[i], "--summary-first") == 0)
 
783
        {
 
784
            summary_first = 1;
 
785
            i++;
 
786
            continue;
 
787
        }
 
788
 
 
789
        if (strcmp(argv[i], "-h") == 0)
 
790
            return usage(NULL, NULL);
 
791
 
 
792
        /* see special handling of --norc in main */
 
793
        if (strcmp(argv[i], "--norc") == 0)
 
794
        {
 
795
            norc = "-f";
 
796
            i++;
 
797
            continue;
 
798
        }
 
799
 
 
800
        if (strcmp(argv[i], "--test-log") == 0)
 
801
        {
 
802
            if (++i >= argc)
 
803
                return usage("argument to --test-log missing", "");
 
804
 
 
805
            test_log_file = argv[i++];
 
806
            continue;
 
807
        }
 
808
 
 
809
        if (strcmp(argv[i], "--diff-opts") == 0)
 
810
        {
 
811
            if (++i >= argc)
 
812
                return usage("argument to --diff-opts missing", "");
 
813
 
 
814
            /* allow diff_opts to be turned off by making empty string
 
815
             * into NULL
 
816
             */
 
817
            if (!strlen(argv[i]))
 
818
                diff_opts = NULL;
 
819
            else
 
820
                diff_opts = argv[i];
 
821
            i++;
 
822
            continue;
 
823
        }
 
824
 
 
825
        if (strcmp(argv[i], "--bkcvs") == 0)
 
826
        {
 
827
            bkcvs = 1;
 
828
            i++;
 
829
            continue;
 
830
        }
 
831
 
 
832
        if (strcmp(argv[i], "--no-rlog") == 0)
 
833
        {
 
834
            no_rlog = 1;
 
835
            i++;
 
836
            continue;
 
837
        }
 
838
 
 
839
        if (strcmp(argv[i], "--cvs-direct") == 0)
 
840
        {
 
841
            cvs_direct = 1;
 
842
            i++;
 
843
            continue;
 
844
        }
 
845
 
 
846
        if (strcmp(argv[i], "--no-cvs-direct") == 0)
 
847
        {
 
848
            cvs_direct = 0;
 
849
            i++;
 
850
            continue;
 
851
        }
 
852
 
 
853
        if (strcmp(argv[i], "--debuglvl") == 0)
 
854
        {
 
855
            if (++i >= argc)
 
856
                return usage("argument to --debuglvl missing", "");
 
857
 
 
858
            debuglvl = atoi(argv[i++]);
 
859
            continue;
 
860
        }
 
861
 
 
862
        if (strcmp(argv[i], "-Z") == 0)
 
863
        {
 
864
            if (++i >= argc)
 
865
                return usage("argument to -Z", "");
 
866
 
 
867
            compress = atoi(argv[i++]);
 
868
 
 
869
            if (compress < 0 || compress > 9)
 
870
                return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
 
871
 
 
872
            if (compress == 0)
 
873
                compress_arg[0] = 0;
 
874
            else
 
875
                snprintf(compress_arg, 8, "-z%d", compress);
 
876
            continue;
 
877
        }
 
878
        
 
879
        if (strcmp(argv[i], "--root") == 0)
 
880
        {
 
881
            if (++i >= argc)
 
882
                return usage("argument to --root missing", "");
 
883
 
 
884
            strcpy(root_path, argv[i++]);
 
885
            continue;
 
886
        }
 
887
 
 
888
        if (strcmp(argv[i], "-q") == 0)
 
889
        {
 
890
            debuglvl &= ~DEBUG_APPMSG1;
 
891
            i++;
 
892
            continue;
 
893
        }
 
894
 
 
895
        if (strcmp(argv[i], "-A") == 0)
 
896
        {
 
897
            track_branch_ancestry = 1;
 
898
            i++;
 
899
            continue;
 
900
        }
 
901
 
 
902
        if (argv[i][0] == '-')
 
903
            return usage("invalid argument", argv[i]);
 
904
        
 
905
        strcpy(repository_path, argv[i++]);
 
906
    }
 
907
 
 
908
    return 0;
 
909
}
 
910
 
 
911
static int parse_rc()
 
912
{
 
913
    char rcfile[PATH_MAX];
 
914
    FILE * fp;
 
915
    snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
 
916
    if ((fp = fopen(rcfile, "r")))
 
917
    {
 
918
        char buff[BUFSIZ];
 
919
        while (fgets(buff, BUFSIZ, fp))
 
920
        {
 
921
            char * argv[3], *p;
 
922
            int argc = 2;
 
923
 
 
924
            chop(buff);
 
925
 
 
926
            argv[0] = "garbage";
 
927
 
 
928
            p = strchr(buff, ' ');
 
929
            if (p)
 
930
            {
 
931
                *p++ = '\0';
 
932
                argv[2] = xstrdup(p);
 
933
                argc = 3;
 
934
            }
 
935
 
 
936
            argv[1] = xstrdup(buff);
 
937
 
 
938
            if (parse_args(argc, argv) < 0)
 
939
                return -1;
 
940
        }
 
941
        fclose(fp);
 
942
    }
 
943
 
 
944
    return 0;
 
945
}
 
946
 
 
947
static void init_paths()
 
948
{
 
949
    FILE * fp;
 
950
    char * p;
 
951
    int len;
 
952
 
 
953
    /* determine the CVSROOT. precedence:
 
954
     * 1) command line
 
955
     * 2) working directory (if present)
 
956
     * 3) environment variable CVSROOT
 
957
     */
 
958
    if (!root_path[0])
 
959
    {
 
960
        if (!(fp = fopen("CVS/Root", "r")))
 
961
        {
 
962
            const char * e;
 
963
 
 
964
            debug(DEBUG_STATUS, "Can't open CVS/Root");
 
965
            e = getenv("CVSROOT");
 
966
 
 
967
            if (!e)
 
968
            {
 
969
                debug(DEBUG_APPERROR, "cannot determine CVSROOT");
 
970
                exit(1);
 
971
            }
 
972
            
 
973
            strcpy(root_path, e);
 
974
        }
 
975
        else
 
976
        {
 
977
            if (!fgets(root_path, PATH_MAX, fp))
 
978
            {
 
979
                debug(DEBUG_APPERROR, "Error reading CVSROOT");
 
980
                exit(1);
 
981
            }
 
982
            
 
983
            fclose(fp);
 
984
            
 
985
            /* chop the lf and optional trailing '/' */
 
986
            len = strlen(root_path) - 1;
 
987
            root_path[len] = 0;
 
988
            if (root_path[len - 1] == '/')
 
989
                root_path[--len] = 0;
 
990
        }
 
991
    }
 
992
 
 
993
    /* Determine the repository path, precedence:
 
994
     * 1) command line
 
995
     * 2) working directory
 
996
     */
 
997
      
 
998
    if (!repository_path[0])
 
999
    {
 
1000
        if (!(fp = fopen("CVS/Repository", "r")))
 
1001
        {
 
1002
            debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
 
1003
            exit(1);
 
1004
        }
 
1005
        
 
1006
        if (!fgets(repository_path, PATH_MAX, fp))
 
1007
        {
 
1008
            debug(DEBUG_APPERROR, "Error reading repository path");
 
1009
            exit(1);
 
1010
        }
 
1011
        
 
1012
        chop(repository_path);
 
1013
        fclose(fp);
 
1014
    }
 
1015
 
 
1016
    /* get the path portion of the root */
 
1017
    p = strrchr(root_path, ':');
 
1018
 
 
1019
    if (!p)
 
1020
        p = root_path;
 
1021
    else 
 
1022
        p++;
 
1023
 
 
1024
    /* some CVS have the CVSROOT string as part of the repository
 
1025
     * string (initial substring).  remove it.
 
1026
     */
 
1027
    len = strlen(p);
 
1028
 
 
1029
    if (strncmp(p, repository_path, len) == 0)
 
1030
    {
 
1031
        int rlen = strlen(repository_path + len + 1);
 
1032
        memmove(repository_path, repository_path + len + 1, rlen + 1);
 
1033
    }
 
1034
 
 
1035
    /* the 'strip_path' will be used whenever the CVS server gives us a
 
1036
     * path to an 'rcs file'.  the strip_path portion of these paths is
 
1037
     * stripped off, leaving us with the working file.
 
1038
     *
 
1039
     * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
 
1040
     * (instead of log) it gives the 'real' RCS file path, which can be different
 
1041
     * from the 'nominal' repository path because of symlinks in the server and 
 
1042
     * the like.  See also the 'parse_file' routine
 
1043
     */
 
1044
    strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
 
1045
 
 
1046
    if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
 
1047
    {
 
1048
        debug(DEBUG_APPERROR, "strip_path overflow");
 
1049
        exit(1);
 
1050
    }
 
1051
 
 
1052
    debug(DEBUG_STATUS, "strip_path: %s", strip_path);
 
1053
}
 
1054
 
 
1055
static CvsFile * parse_file(const char * buff)
 
1056
{
 
1057
    CvsFile * retval;
 
1058
    char fn[PATH_MAX];
 
1059
    int len = strlen(buff + 10);
 
1060
    char * p;
 
1061
 
 
1062
    /* once a single file has been parsed ok we set this */
 
1063
    static int path_ok;
 
1064
    
 
1065
    /* chop the ",v" string and the "LF" */
 
1066
    len -= 3;
 
1067
    memcpy(fn, buff + 10, len);
 
1068
    fn[len] = 0;
 
1069
    
 
1070
    if (strncmp(fn, strip_path, strip_path_len) != 0)
 
1071
    {
 
1072
        /* if the very first file fails the strip path,
 
1073
         * then maybe we need to try for an alternate.
 
1074
         * this will happen if symlinks are being used
 
1075
         * on the server.  our best guess is to look
 
1076
         * for the final occurance of the repository
 
1077
         * path in the filename and use that.  it should work
 
1078
         * except in the case where:
 
1079
         * 1) the project has no files in the top-level directory
 
1080
         * 2) the project has a directory with the same name as the project
 
1081
         * 3) that directory sorts alphabetically before any other directory
 
1082
         * in which case, you are scr**ed
 
1083
         */
 
1084
        if (!path_ok)
 
1085
        {
 
1086
            char * p = fn, *lastp = NULL;
 
1087
 
 
1088
            while ((p = strstr(p, repository_path)))
 
1089
                lastp = p++;
 
1090
      
 
1091
            if (lastp)
 
1092
            {
 
1093
                int len = strlen(repository_path);
 
1094
                memcpy(strip_path, fn, lastp - fn + len + 1);
 
1095
                strip_path_len = lastp - fn + len + 1;
 
1096
                strip_path[strip_path_len] = 0;
 
1097
                debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
 
1098
                goto ok;
 
1099
            }
 
1100
        }
 
1101
 
 
1102
        /* FIXME: a subdirectory may have a different Repository path
 
1103
         * than it's parent.  we'll fail the above test since strip_path
 
1104
         * is global for the entire checked out tree (recursively).
 
1105
         *
 
1106
         * For now just ignore such files
 
1107
         */
 
1108
        debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring", 
 
1109
              fn, strip_path);
 
1110
        return NULL;
 
1111
    }
 
1112
 
 
1113
 ok:
 
1114
    path_ok = 1;
 
1115
 
 
1116
    /* remove from beginning the 'strip_path' string */
 
1117
    len -= strip_path_len;
 
1118
    memmove(fn, fn + strip_path_len, len);
 
1119
    fn[len] = 0;
 
1120
 
 
1121
    /* check if file is in the 'Attic/' and remove it */
 
1122
    if ((p = strrchr(fn, '/')) &&
 
1123
        p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
 
1124
    {
 
1125
        memmove(p - 5, p + 1, len - (p - fn + 1));
 
1126
        len -= 6;
 
1127
        fn[len] = 0;
 
1128
    }
 
1129
 
 
1130
    debug(DEBUG_STATUS, "stripped filename %s", fn);
 
1131
 
 
1132
    retval = (CvsFile*)get_hash_object(file_hash, fn);
 
1133
 
 
1134
    if (!retval)
 
1135
    {
 
1136
        if ((retval = create_cvsfile()))
 
1137
        {
 
1138
            retval->filename = xstrdup(fn);
 
1139
            put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
 
1140
        }
 
1141
        else
 
1142
        {
 
1143
            debug(DEBUG_SYSERROR, "malloc failed");
 
1144
            exit(1);
 
1145
        }
 
1146
 
 
1147
        debug(DEBUG_STATUS, "new file: %s", retval->filename);
 
1148
    }
 
1149
    else
 
1150
    {
 
1151
        debug(DEBUG_STATUS, "existing file: %s", retval->filename);
 
1152
    }
 
1153
 
 
1154
    return retval;
 
1155
}
 
1156
 
 
1157
PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
 
1158
{
 
1159
    PatchSet * retval = NULL, **find = NULL;
 
1160
    int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
 
1161
 
 
1162
    if (!(retval = create_patch_set()))
 
1163
    {
 
1164
        debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
 
1165
        return NULL;
 
1166
    }
 
1167
 
 
1168
    convert_date(&retval->date, dte);
 
1169
    retval->author = get_string(author);
 
1170
    retval->descr = xstrdup(log);
 
1171
    retval->branch = get_string(branch);
 
1172
    
 
1173
    /* we are looking for a patchset suitable for holding this member.
 
1174
     * this means two things:
 
1175
     * 1) a patchset already containing an entry for the file is no good
 
1176
     * 2) for two patchsets with same exact date/time, if they reference 
 
1177
     *    the same file, we can properly order them.  this primarily solves
 
1178
     *    the 'cvs import' problem and may not have general usefulness
 
1179
     *    because it would only work if the first member we consider is
 
1180
     *    present in the existing ps.
 
1181
     */
 
1182
    if (psm)
 
1183
        list_add(&psm->link, retval->members.prev);
 
1184
 
 
1185
    find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
 
1186
 
 
1187
    if (psm)
 
1188
        list_del(&psm->link);
 
1189
 
 
1190
    if (*find != retval)
 
1191
    {
 
1192
        debug(DEBUG_STATUS, "found existing patch set");
 
1193
 
 
1194
        if (bkcvs && strstr(retval->descr, "BKrev:"))
 
1195
        {
 
1196
            free((*find)->descr);
 
1197
            (*find)->descr = retval->descr;
 
1198
        }
 
1199
        else
 
1200
        {
 
1201
            free(retval->descr);
 
1202
        }
 
1203
 
 
1204
        /* keep the minimum date of any member as the 'actual' date */
 
1205
        if (retval->date < (*find)->date)
 
1206
            (*find)->date = retval->date;
 
1207
 
 
1208
        /* expand the min_date/max_date window to help finding other members .
 
1209
         * open the window by an extra margin determined by the fuzz factor 
 
1210
         */
 
1211
        if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
 
1212
        {
 
1213
            (*find)->min_date = retval->date - timestamp_fuzz_factor;
 
1214
            //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
 
1215
        }
 
1216
        else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
 
1217
            (*find)->max_date = retval->date + timestamp_fuzz_factor;
 
1218
 
 
1219
        free(retval);
 
1220
        retval = *find;
 
1221
    }
 
1222
    else
 
1223
    {
 
1224
        debug(DEBUG_STATUS, "new patch set!");
 
1225
        debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
 
1226
 
 
1227
        retval->min_date = retval->date - timestamp_fuzz_factor;
 
1228
        retval->max_date = retval->date + timestamp_fuzz_factor;
 
1229
 
 
1230
        list_add(&retval->all_link, &all_patch_sets);
 
1231
    }
 
1232
 
 
1233
 
 
1234
    return retval;
 
1235
}
 
1236
 
 
1237
static int get_branch_ext(char * buff, const char * rev, int * leaf)
 
1238
{
 
1239
    char * p;
 
1240
    int len = strlen(rev);
 
1241
 
 
1242
    /* allow get_branch(buff, buff) without destroying contents */
 
1243
    memmove(buff, rev, len);
 
1244
    buff[len] = 0;
 
1245
 
 
1246
    p = strrchr(buff, '.');
 
1247
    if (!p)
 
1248
        return 0;
 
1249
    *p++ = 0;
 
1250
 
 
1251
    if (leaf)
 
1252
        *leaf = atoi(p);
 
1253
 
 
1254
    return 1;
 
1255
}
 
1256
 
 
1257
static int get_branch(char * buff, const char * rev)
 
1258
{
 
1259
    return get_branch_ext(buff, rev, NULL);
 
1260
}
 
1261
 
 
1262
/* 
 
1263
 * the goal if this function is to determine what revision to assign to
 
1264
 * the psm->pre_rev field.  usually, the log file is strictly 
 
1265
 * reverse chronological, so rev is direct ancestor to psm, 
 
1266
 * 
 
1267
 * This all breaks down at branch points however
 
1268
 */
 
1269
 
 
1270
static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
 
1271
{
 
1272
    char pre[REV_STR_MAX], post[REV_STR_MAX];
 
1273
 
 
1274
    if (!psm)
 
1275
        return;
 
1276
    
 
1277
    if (!rev)
 
1278
    {
 
1279
        /* if psm was last rev. for file, it's either an 
 
1280
         * INITIAL, or first rev of a branch.  to test if it's 
 
1281
         * the first rev of a branch, do get_branch twice - 
 
1282
         * this should be the bp.
 
1283
         */
 
1284
        if (get_branch(post, psm->post_rev->rev) && 
 
1285
            get_branch(pre, post))
 
1286
        {
 
1287
            psm->pre_rev = file_get_revision(psm->file, pre);
 
1288
            list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
 
1289
        }
 
1290
        else
 
1291
        {
 
1292
            set_psm_initial(psm);
 
1293
        }
 
1294
        return;
 
1295
    }
 
1296
 
 
1297
    /* 
 
1298
     * is this canditate for 'pre' on the same branch as our 'post'? 
 
1299
     * this is the normal case
 
1300
     */
 
1301
    if (!get_branch(pre, rev->rev))
 
1302
    {
 
1303
        debug(DEBUG_APPERROR, "get_branch malformed input (1)");
 
1304
        return;
 
1305
    }
 
1306
 
 
1307
    if (!get_branch(post, psm->post_rev->rev))
 
1308
    {
 
1309
        debug(DEBUG_APPERROR, "get_branch malformed input (2)");
 
1310
        return;
 
1311
    }
 
1312
 
 
1313
    if (strcmp(pre, post) == 0)
 
1314
    {
 
1315
        psm->pre_rev = rev;
 
1316
        rev->pre_psm = psm;
 
1317
        return;
 
1318
    }
 
1319
    
 
1320
    /* branches don't match. new_psm must be head of branch,
 
1321
     * so psm is oldest rev. on branch. or oldest
 
1322
     * revision overall.  if former, derive predecessor.  
 
1323
     * use get_branch to chop another rev. off of string.
 
1324
     *
 
1325
     * FIXME:
 
1326
     * There's also a weird case.  it's possible to just re-number
 
1327
     * a revision to any future revision. i.e. rev 1.9 becomes 2.0
 
1328
     * It's not widely used.  In those cases of discontinuity,
 
1329
     * we end up stamping the predecessor as 'INITIAL' incorrectly
 
1330
     *
 
1331
     */
 
1332
    if (!get_branch(pre, post))
 
1333
    {
 
1334
        set_psm_initial(psm);
 
1335
        return;
 
1336
    }
 
1337
    
 
1338
    psm->pre_rev = file_get_revision(psm->file, pre);
 
1339
    list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
 
1340
}
 
1341
 
 
1342
static void check_print_patch_set(PatchSet * ps)
 
1343
{
 
1344
    if (ps->psid < 0)
 
1345
        return;
 
1346
 
 
1347
    /* the funk_factor overrides the restrict_tag_start and end */
 
1348
    if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
 
1349
        goto ok;
 
1350
 
 
1351
    if (ps->funk_factor == FNK_HIDE_ALL)
 
1352
        return;
 
1353
 
 
1354
    if (ps->psid <= restrict_tag_ps_start)
 
1355
    {
 
1356
        if (ps->psid == restrict_tag_ps_start)
 
1357
            debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
 
1358
        
 
1359
        return;
 
1360
    }
 
1361
    
 
1362
    if (ps->psid > restrict_tag_ps_end)
 
1363
        return;
 
1364
 
 
1365
 ok:
 
1366
    if (restrict_date_start > 0 &&
 
1367
        (ps->date < restrict_date_start ||
 
1368
         (restrict_date_end > 0 && ps->date > restrict_date_end)))
 
1369
        return;
 
1370
 
 
1371
    if (restrict_author && strcmp(restrict_author, ps->author) != 0)
 
1372
        return;
 
1373
 
 
1374
    if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
 
1375
        return;
 
1376
 
 
1377
    if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
 
1378
        return;
 
1379
 
 
1380
    if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
 
1381
        return;
 
1382
    
 
1383
    if (!list_empty(&show_patch_set_ranges))
 
1384
    {
 
1385
        struct list_head * next = show_patch_set_ranges.next;
 
1386
        
 
1387
        while (next != &show_patch_set_ranges)
 
1388
        {
 
1389
            PatchSetRange *range = list_entry(next, PatchSetRange, link);
 
1390
            if (range->min_counter <= ps->psid &&
 
1391
                ps->psid <= range->max_counter)
 
1392
            {
 
1393
                break;
 
1394
            }
 
1395
            next = next->next;
 
1396
        }
 
1397
        
 
1398
        if (next == &show_patch_set_ranges)
 
1399
            return;
 
1400
    }
 
1401
 
 
1402
    if (patch_set_dir)
 
1403
    {
 
1404
        char path[PATH_MAX];
 
1405
 
 
1406
        snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
 
1407
 
 
1408
        fflush(stdout);
 
1409
        close(1);
 
1410
        if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
 
1411
        {
 
1412
            debug(DEBUG_SYSERROR, "can't open patch file %s", path);
 
1413
            exit(1);
 
1414
        }
 
1415
 
 
1416
        fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
 
1417
    }
 
1418
 
 
1419
    /*
 
1420
     * If the summary_first option is in effect, there will be 
 
1421
     * two passes through the tree.  the first with summary_first == 1
 
1422
     * the second with summary_first == 2.  if the option is not
 
1423
     * in effect, there will be one pass with summary_first == 0
 
1424
     *
 
1425
     * When the -s option is in effect, the show_patch_set_ranges
 
1426
     * list will be non-empty.
 
1427
     */
 
1428
    if (summary_first <= 1)
 
1429
        print_patch_set(ps);
 
1430
    if (do_diff && summary_first != 1)
 
1431
        do_cvs_diff(ps);
 
1432
 
 
1433
    fflush(stdout);
 
1434
}
 
1435
 
 
1436
static void print_patch_set(PatchSet * ps)
 
1437
{
 
1438
    struct tm *tm;
 
1439
    struct list_head * next;
 
1440
    const char * funk = "";
 
1441
 
 
1442
    tm = localtime(&ps->date);
 
1443
    next = ps->members.next;
 
1444
    
 
1445
    funk = fnk_descr[ps->funk_factor];
 
1446
    
 
1447
    /* this '---...' is different from the 28 hyphens that separate cvs log output */
 
1448
    printf("---------------------\n");
 
1449
    printf("PatchSet %d %s\n", ps->psid, funk);
 
1450
    printf("Date: %d/%02d/%02d %02d:%02d:%02d\n", 
 
1451
           1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, 
 
1452
           tm->tm_hour, tm->tm_min, tm->tm_sec);
 
1453
    printf("Author: %s\n", ps->author);
 
1454
    printf("Branch: %s\n", ps->branch);
 
1455
    if (ps->ancestor_branch)
 
1456
        printf("Ancestor branch: %s\n", ps->ancestor_branch);
 
1457
    printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
 
1458
    printf("Log:\n%s\n", ps->descr);
 
1459
    printf("Members: \n");
 
1460
 
 
1461
    while (next != &ps->members)
 
1462
    {
 
1463
        PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
1464
        if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
 
1465
            funk = "(BEFORE START TAG)";
 
1466
        else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
 
1467
            funk = "(AFTER END TAG)";
 
1468
        else
 
1469
            funk = "";
 
1470
 
 
1471
        printf("\t%s:%s->%s%s %s\n", 
 
1472
               psm->file->filename, 
 
1473
               psm->pre_rev ? psm->pre_rev->rev : "INITIAL", 
 
1474
               psm->post_rev->rev, 
 
1475
               psm->post_rev->dead ? "(DEAD)": "",
 
1476
               funk);
 
1477
 
 
1478
        next = next->next;
 
1479
    }
 
1480
    
 
1481
    printf("\n");
 
1482
}
 
1483
 
 
1484
/* walk all the patchsets to assign monotonic psid, 
 
1485
 * and to establish  branch ancestry
 
1486
 */
 
1487
static void assign_patchset_id(PatchSet * ps)
 
1488
{
 
1489
    /*
 
1490
     * Ignore the 'BRANCH ADD' patchsets 
 
1491
     */
 
1492
    if (!ps->branch_add)
 
1493
    {
 
1494
        ps_counter++;
 
1495
        ps->psid = ps_counter;
 
1496
        
 
1497
        if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
 
1498
        {
 
1499
            PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
 
1500
            if (!head_ps) 
 
1501
            {
 
1502
                head_ps = ps;
 
1503
                put_hash_object(branch_heads, ps->branch, head_ps);
 
1504
            }
 
1505
            
 
1506
            determine_branch_ancestor(ps, head_ps);
 
1507
        }
 
1508
    }
 
1509
    else
 
1510
    {
 
1511
        ps->psid = -1;
 
1512
    }
 
1513
}
 
1514
 
 
1515
static int compare_rev_strings(const char * cr1, const char * cr2)
 
1516
{
 
1517
    char r1[REV_STR_MAX];
 
1518
    char r2[REV_STR_MAX];
 
1519
    char *s1 = r1, *s2 = r2;
 
1520
    char *p1, *p2;
 
1521
    int n1, n2;
 
1522
 
 
1523
    strcpy(s1, cr1);
 
1524
    strcpy(s2, cr2);
 
1525
 
 
1526
    for (;;) 
 
1527
    {
 
1528
        p1 = strchr(s1, '.');
 
1529
        p2 = strchr(s2, '.');
 
1530
 
 
1531
        if (p1) *p1++ = 0;
 
1532
        if (p2) *p2++ = 0;
 
1533
        
 
1534
        n1 = atoi(s1);
 
1535
        n2 = atoi(s2);
 
1536
        
 
1537
        if (n1 < n2)
 
1538
            return -1;
 
1539
        if (n1 > n2)
 
1540
            return 1;
 
1541
 
 
1542
        if (!p1 && p2)
 
1543
            return -1;
 
1544
        if (p1 && !p2)
 
1545
            return 1;
 
1546
        if (!p1 && !p2)
 
1547
            return 0;
 
1548
 
 
1549
        s1 = p1;
 
1550
        s2 = p2;
 
1551
    }
 
1552
}
 
1553
 
 
1554
static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
 
1555
{
 
1556
    struct list_head * i;
 
1557
 
 
1558
    for (i = ps1->members.next; i != &ps1->members; i = i->next)
 
1559
    {
 
1560
        PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
 
1561
        struct list_head * j;
 
1562
 
 
1563
        for (j = ps2->members.next; j != &ps2->members; j = j->next)
 
1564
        {
 
1565
            PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
 
1566
            if (psm1->file == psm2->file) 
 
1567
            {
 
1568
                int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
 
1569
                //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
 
1570
                return ret;
 
1571
            }
 
1572
        }
 
1573
    }
 
1574
    
 
1575
    return 0;
 
1576
}
 
1577
 
 
1578
static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
 
1579
{
 
1580
    const PatchSet * ps1 = (const PatchSet *)v_ps1;
 
1581
    const PatchSet * ps2 = (const PatchSet *)v_ps2;
 
1582
    long diff;
 
1583
 
 
1584
    diff = ps1->date - ps2->date;
 
1585
 
 
1586
    return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
 
1587
}
 
1588
 
 
1589
static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
 
1590
{
 
1591
    const PatchSet * ps1 = (const PatchSet *)v_ps1;
 
1592
    const PatchSet * ps2 = (const PatchSet *)v_ps2;
 
1593
    long diff;
 
1594
    int ret;
 
1595
    time_t d, min, max;
 
1596
 
 
1597
    /* We order by (author, descr, branch, members, date), but because of the fuzz factor
 
1598
     * we treat times within a certain distance as equal IFF the author
 
1599
     * and descr match.
 
1600
     */
 
1601
 
 
1602
    ret = strcmp(ps1->author, ps2->author);
 
1603
    if (ret)
 
1604
            return ret;
 
1605
 
 
1606
    ret = strcmp(ps1->descr, ps2->descr);
 
1607
    if (ret)
 
1608
            return ret;
 
1609
 
 
1610
    ret = strcmp(ps1->branch, ps2->branch);
 
1611
    if (ret)
 
1612
        return ret;
 
1613
 
 
1614
    ret = compare_patch_sets_by_members(ps1, ps2);
 
1615
    if (ret)
 
1616
        return ret;
 
1617
 
 
1618
    /* 
 
1619
     * one of ps1 or ps2 is new.  the other should have the min_date
 
1620
     * and max_date set to a window opened by the fuzz_factor
 
1621
     */
 
1622
    if (ps1->min_date == 0)
 
1623
    {
 
1624
        d = ps1->date;
 
1625
        min = ps2->min_date;
 
1626
        max = ps2->max_date;
 
1627
    } 
 
1628
    else if (ps2->min_date == 0)
 
1629
    {
 
1630
        d = ps2->date;
 
1631
        min = ps1->min_date;
 
1632
        max = ps1->max_date;
 
1633
    }
 
1634
    else
 
1635
    {
 
1636
        debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
 
1637
        exit(1);
 
1638
    }
 
1639
 
 
1640
    if (min < d && d < max)
 
1641
        return 0;
 
1642
 
 
1643
    diff = ps1->date - ps2->date;
 
1644
 
 
1645
    return (diff < 0) ? -1 : 1;
 
1646
}
 
1647
 
 
1648
static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
 
1649
{
 
1650
    const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
 
1651
    const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
 
1652
    return compare_patch_sets_bytime(ps1, ps2);
 
1653
}
 
1654
 
 
1655
static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
 
1656
{
 
1657
    long diff;
 
1658
    int ret;
 
1659
 
 
1660
    /* When doing a time-ordering of patchsets, we don't need to
 
1661
     * fuzzy-match the time.  We've already done fuzzy-matching so we
 
1662
     * know that insertions are unique at this point.
 
1663
     */
 
1664
 
 
1665
    diff = ps1->date - ps2->date;
 
1666
    if (diff)
 
1667
        return (diff < 0) ? -1 : 1;
 
1668
 
 
1669
    ret = compare_patch_sets_by_members(ps1, ps2);
 
1670
    if (ret)
 
1671
        return ret;
 
1672
 
 
1673
    ret = strcmp(ps1->author, ps2->author);
 
1674
    if (ret)
 
1675
        return ret;
 
1676
 
 
1677
    ret = strcmp(ps1->descr, ps2->descr);
 
1678
    if (ret)
 
1679
        return ret;
 
1680
 
 
1681
    ret = strcmp(ps1->branch, ps2->branch);
 
1682
    return ret;
 
1683
}
 
1684
 
 
1685
 
 
1686
static int is_revision_metadata(const char * buff)
 
1687
{
 
1688
    char * p1, *p2;
 
1689
    int len;
 
1690
 
 
1691
    if (!(p1 = strchr(buff, ':')))
 
1692
        return 0;
 
1693
 
 
1694
    p2 = strchr(buff, ' ');
 
1695
    
 
1696
    if (p2 && p2 < p1)
 
1697
        return 0;
 
1698
 
 
1699
    len = strlen(buff);
 
1700
 
 
1701
    /* lines have LF at end */
 
1702
    if (len > 1 && buff[len - 2] == ';')
 
1703
        return 1;
 
1704
 
 
1705
    return 0;
 
1706
}
 
1707
 
 
1708
static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
 
1709
{
 
1710
    struct list_head * next = ps->members.next;
 
1711
 
 
1712
    while (next != &ps->members)
 
1713
    {
 
1714
        PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
1715
        
 
1716
        if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
 
1717
            return 1;
 
1718
 
 
1719
        next = next->next;
 
1720
    }
 
1721
 
 
1722
    return 0;
 
1723
}
 
1724
 
 
1725
static int patch_set_affects_branch(PatchSet * ps, const char * branch)
 
1726
{
 
1727
    struct list_head * next;
 
1728
 
 
1729
    for (next = ps->members.next; next != &ps->members; next = next->next)
 
1730
    {
 
1731
        PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
1732
 
 
1733
        /*
 
1734
         * slight hack. if -r is specified, and this patchset
 
1735
         * is 'before' the tag, but is FNK_SHOW_SOME, only
 
1736
         * check if the 'after tag' revisions affect
 
1737
         * the branch.  this is especially important when
 
1738
         * the tag is a branch point.
 
1739
         */
 
1740
        if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
 
1741
            continue;
 
1742
 
 
1743
        if (revision_affects_branch(psm->post_rev, branch))
 
1744
            return 1;
 
1745
    }
 
1746
 
 
1747
    return 0;
 
1748
}
 
1749
 
 
1750
static void do_cvs_diff(PatchSet * ps)
 
1751
{
 
1752
    struct list_head * next;
 
1753
    const char * dtype;
 
1754
    const char * dopts;
 
1755
    const char * utype;
 
1756
    char use_rep_path[PATH_MAX];
 
1757
    char esc_use_rep_path[PATH_MAX];
 
1758
 
 
1759
    fflush(stdout);
 
1760
    fflush(stderr);
 
1761
 
 
1762
    /* 
 
1763
     * if cvs_direct is not in effect, and diff options are specified,
 
1764
     * then we have to use diff instead of rdiff and we'll get a -p0 
 
1765
     * diff (instead of -p1) [in a manner of speaking].  So to make sure
 
1766
     * that the add/remove diffs get generated likewise, we need to use
 
1767
     * 'update' instead of 'co' 
 
1768
     *
 
1769
     * cvs_direct will always use diff (not rdiff), but will also always
 
1770
     * generate -p1 diffs.
 
1771
     */
 
1772
    if (diff_opts == NULL) 
 
1773
    {
 
1774
        dopts = "-u";
 
1775
        dtype = "rdiff";
 
1776
        utype = "co";
 
1777
        sprintf(use_rep_path, "%s/", repository_path);
 
1778
        /* the rep_path may contain characters that the shell will barf on */
 
1779
        escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
 
1780
    }
 
1781
    else
 
1782
    {
 
1783
        dopts = diff_opts;
 
1784
        dtype = "diff";
 
1785
        utype = "update";
 
1786
        use_rep_path[0] = 0;
 
1787
        esc_use_rep_path[0] = 0;
 
1788
    }
 
1789
 
 
1790
    for (next = ps->members.next; next != &ps->members; next = next->next)
 
1791
    {
 
1792
        PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
1793
        char cmdbuff[PATH_MAX * 2+1];
 
1794
        char esc_file[PATH_MAX];
 
1795
        int ret, check_ret = 0;
 
1796
 
 
1797
        cmdbuff[0] = 0;
 
1798
        cmdbuff[PATH_MAX*2] = 0;
 
1799
 
 
1800
        /* the filename may contain characters that the shell will barf on */
 
1801
        escape_filename(esc_file, PATH_MAX, psm->file->filename);
 
1802
 
 
1803
        /*
 
1804
         * Check the patchset funk. we may not want to diff this particular file 
 
1805
         */
 
1806
        if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
 
1807
        {
 
1808
            printf("Index: %s\n", psm->file->filename);
 
1809
            printf("===================================================================\n");
 
1810
            printf("*** Member not diffed, before start tag\n");
 
1811
            continue;
 
1812
        }
 
1813
        else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
 
1814
        {
 
1815
            printf("Index: %s\n", psm->file->filename);
 
1816
            printf("===================================================================\n");
 
1817
            printf("*** Member not diffed, after end tag\n");
 
1818
            continue;
 
1819
        }
 
1820
 
 
1821
        /* 
 
1822
         * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
 
1823
         * or 'cvs update' to get the file, because cvs won't generate these diffs.
 
1824
         * The problem is that this must be piped to diff, and so the resulting
 
1825
         * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
 
1826
         * sed is used to replace the '-' with the filename. 
 
1827
         *
 
1828
         * It's possible for pre_rev to be a 'dead' revision. This happens when a file 
 
1829
         * is added on a branch. post_rev will be dead dead for remove
 
1830
         */
 
1831
        if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
 
1832
        {
 
1833
            int cr;
 
1834
            const char * rev;
 
1835
 
 
1836
            if (!psm->pre_rev || psm->pre_rev->dead)
 
1837
            {
 
1838
                cr = 1;
 
1839
                rev = psm->post_rev->rev;
 
1840
            }
 
1841
            else
 
1842
            {
 
1843
                cr = 0;
 
1844
                rev = psm->pre_rev->rev;
 
1845
            }
 
1846
 
 
1847
            if (cvs_direct_ctx)
 
1848
            {
 
1849
                /* cvs_rupdate does the pipe through diff thing internally */
 
1850
                cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
 
1851
            }
 
1852
            else
 
1853
            {
 
1854
                snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s -p -r %s %s%s | diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s%s|g'",
 
1855
                         compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
 
1856
                         cr?"":"-",cr?"-":"", cr?"2":"1",
 
1857
                         use_rep_path, psm->file->filename);
 
1858
            }
 
1859
        }
 
1860
        else
 
1861
        {
 
1862
            /* a regular diff */
 
1863
            if (cvs_direct_ctx)
 
1864
            {
 
1865
                cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
 
1866
            }
 
1867
            else
 
1868
            {
 
1869
                /* 'cvs diff' exit status '1' is ok, just means files are different */
 
1870
                if (strcmp(dtype, "diff") == 0)
 
1871
                    check_ret = 1;
 
1872
 
 
1873
                snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
 
1874
                         compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev, 
 
1875
                         esc_use_rep_path, esc_file);
 
1876
            }
 
1877
        }
 
1878
 
 
1879
        /*
 
1880
         * my_system doesn't block signals the way system does.
 
1881
         * if ctrl-c is pressed while in there, we probably exit
 
1882
         * immediately and hope the shell has sent the signal
 
1883
         * to all of the process group members
 
1884
         */
 
1885
        if (cmdbuff[0] && (ret = my_system(cmdbuff)))
 
1886
        {
 
1887
            int stat = WEXITSTATUS(ret);
 
1888
            
 
1889
            /* 
 
1890
             * cvs diff returns 1 in exit status for 'files are different'
 
1891
             * so use a better method to check for failure
 
1892
             */
 
1893
            if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
 
1894
            {
 
1895
                debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
 
1896
                exit(1);
 
1897
            }
 
1898
        }
 
1899
    }
 
1900
}
 
1901
 
 
1902
static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
 
1903
{
 
1904
    char * p;
 
1905
 
 
1906
    /* The "revision" log line can include extra information 
 
1907
     * including who is locking the file --- strip that out.
 
1908
     */
 
1909
    
 
1910
    p = rev_str;
 
1911
    while (isdigit(*p) || *p == '.')
 
1912
            p++;
 
1913
    *p = 0;
 
1914
 
 
1915
    return cvs_file_add_revision(file, rev_str);
 
1916
}
 
1917
 
 
1918
CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
 
1919
{
 
1920
    CvsFileRevision * rev;
 
1921
 
 
1922
    if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
 
1923
    {
 
1924
        rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
 
1925
        rev->rev = get_string(rev_str);
 
1926
        rev->file = file;
 
1927
        rev->branch = NULL;
 
1928
        rev->present = 0;
 
1929
        rev->pre_psm = NULL;
 
1930
        rev->post_psm = NULL;
 
1931
        INIT_LIST_HEAD(&rev->branch_children);
 
1932
        INIT_LIST_HEAD(&rev->tags);
 
1933
        
 
1934
        put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
 
1935
 
 
1936
        debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
 
1937
    }
 
1938
    else
 
1939
    {
 
1940
        debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
 
1941
    }
 
1942
 
 
1943
    /* 
 
1944
     * note: we are guaranteed to get here at least once with 'have_branches' == 1.
 
1945
     * we may pass through once before this, because of symbolic tags, then once
 
1946
     * always when processing the actual revision logs
 
1947
     *
 
1948
     * rev->branch will always be set to something, maybe "HEAD"
 
1949
     */
 
1950
    if (!rev->branch && file->have_branches)
 
1951
    {
 
1952
        char branch_str[REV_STR_MAX];
 
1953
 
 
1954
        /* in the cvs cvs repository (ccvs) there are tagged versions
 
1955
         * that don't exist.  let's mark every 'known to exist' 
 
1956
         * version
 
1957
         */
 
1958
        rev->present = 1;
 
1959
 
 
1960
        /* determine the branch this revision was committed on */
 
1961
        if (!get_branch(branch_str, rev->rev))
 
1962
        {
 
1963
            debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
 
1964
            exit(1);
 
1965
        }
 
1966
        
 
1967
        rev->branch = (char*)get_hash_object(file->branches, branch_str);
 
1968
        
 
1969
        /* if there's no branch and it's not on the trunk, blab */
 
1970
        if (!rev->branch)
 
1971
        {
 
1972
            if (get_branch(branch_str, branch_str))
 
1973
            {
 
1974
                debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
 
1975
                rev->branch = "#CVSPS_NO_BRANCH";
 
1976
            }
 
1977
            else
 
1978
            {
 
1979
                rev->branch = "HEAD";
 
1980
            }
 
1981
        }
 
1982
 
 
1983
        debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
 
1984
    }
 
1985
 
 
1986
    return rev;
 
1987
}
 
1988
 
 
1989
CvsFile * create_cvsfile()
 
1990
{
 
1991
    CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
 
1992
    if (!f)
 
1993
        return NULL;
 
1994
 
 
1995
    f->revisions = create_hash_table(53);
 
1996
    f->branches = create_hash_table(13);
 
1997
    f->branches_sym = create_hash_table(13);
 
1998
    f->symbols = create_hash_table(253);
 
1999
    f->have_branches = 0;
 
2000
 
 
2001
    if (!f->revisions || !f->branches || !f->branches_sym)
 
2002
    {
 
2003
        if (f->branches)
 
2004
            destroy_hash_table(f->branches, NULL);
 
2005
        if (f->revisions)
 
2006
            destroy_hash_table(f->revisions, NULL);
 
2007
        free(f);
 
2008
        return NULL;
 
2009
    }
 
2010
   
 
2011
    return f;
 
2012
}
 
2013
 
 
2014
static PatchSet * create_patch_set()
 
2015
{
 
2016
    PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
 
2017
    
 
2018
    if (ps)
 
2019
    {
 
2020
        INIT_LIST_HEAD(&ps->members);
 
2021
        ps->psid = -1;
 
2022
        ps->date = 0;
 
2023
        ps->min_date = 0;
 
2024
        ps->max_date = 0;
 
2025
        ps->descr = NULL;
 
2026
        ps->author = NULL;
 
2027
        ps->tag = NULL;
 
2028
        ps->tag_flags = 0;
 
2029
        ps->branch_add = 0;
 
2030
        ps->funk_factor = 0;
 
2031
        ps->ancestor_branch = NULL;
 
2032
        CLEAR_LIST_NODE(&ps->collision_link);
 
2033
    }
 
2034
 
 
2035
    return ps;
 
2036
}
 
2037
 
 
2038
PatchSetMember * create_patch_set_member()
 
2039
{
 
2040
    PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
 
2041
    psm->pre_rev = NULL;
 
2042
    psm->post_rev = NULL;
 
2043
    psm->ps = NULL;
 
2044
    psm->file = NULL;
 
2045
    psm->bad_funk = 0;
 
2046
    return psm;
 
2047
}
 
2048
 
 
2049
static PatchSetRange * create_patch_set_range()
 
2050
{
 
2051
    PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
 
2052
    return psr;
 
2053
}
 
2054
 
 
2055
CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
 
2056
{
 
2057
    CvsFileRevision * rev;
 
2058
 
 
2059
    if (strcmp(r, "INITIAL") == 0)
 
2060
        return NULL;
 
2061
 
 
2062
    rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
 
2063
    
 
2064
    if (!rev)
 
2065
    {
 
2066
        debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
 
2067
        exit(1);
 
2068
    }
 
2069
 
 
2070
    return rev;
 
2071
}
 
2072
 
 
2073
/*
 
2074
 * Parse lines in the format:
 
2075
 * 
 
2076
 * <white space>tag_name: <rev>;
 
2077
 *
 
2078
 * Handles both regular tags (these go into the symbols hash)
 
2079
 * and magic-branch-tags (second to last node of revision is 0)
 
2080
 * which go into branches and branches_sym hashes.  Magic-branch
 
2081
 * format is hidden in CVS everwhere except the 'cvs log' output.
 
2082
 */
 
2083
 
 
2084
static void parse_sym(CvsFile * file, char * sym)
 
2085
{
 
2086
    char * tag = sym, *eot;
 
2087
    int leaf, final_branch = -1;
 
2088
    char rev[REV_STR_MAX];
 
2089
    char rev2[REV_STR_MAX];
 
2090
    
 
2091
    while (*tag && isspace(*tag))
 
2092
        tag++;
 
2093
 
 
2094
    if (!*tag)
 
2095
        return;
 
2096
 
 
2097
    eot = strchr(tag, ':');
 
2098
    
 
2099
    if (!eot)
 
2100
        return;
 
2101
 
 
2102
    *eot = 0;
 
2103
    eot += 2;
 
2104
    
 
2105
    if (!get_branch_ext(rev, eot, &leaf))
 
2106
    {
 
2107
        debug(DEBUG_APPERROR, "malformed revision");
 
2108
        exit(1);
 
2109
    }
 
2110
 
 
2111
    /* 
 
2112
     * get_branch_ext will leave final_branch alone
 
2113
     * if there aren't enough '.' in string 
 
2114
     */
 
2115
    get_branch_ext(rev2, rev, &final_branch);
 
2116
 
 
2117
    if (final_branch == 0)
 
2118
    {
 
2119
        snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
 
2120
        debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
 
2121
        
 
2122
        cvs_file_add_branch(file, rev, tag);
 
2123
    }
 
2124
    else
 
2125
    {
 
2126
        strcpy(rev, eot);
 
2127
        chop(rev);
 
2128
 
 
2129
        /* see cvs manual: what is this vendor tag? */
 
2130
        if (is_vendor_branch(rev))
 
2131
            cvs_file_add_branch(file, rev, tag);
 
2132
        else
 
2133
            cvs_file_add_symbol(file, rev, tag);
 
2134
    }
 
2135
}
 
2136
 
 
2137
void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
 
2138
{
 
2139
    CvsFileRevision * rev;
 
2140
    GlobalSymbol * sym;
 
2141
    Tag * tag;
 
2142
 
 
2143
    /* get a permanent storage string */
 
2144
    char * tag_str = get_string(p_tag_str);
 
2145
 
 
2146
    debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
 
2147
    rev = cvs_file_add_revision(file, rev_str);
 
2148
    put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
 
2149
    
 
2150
    /*
 
2151
     * check the global_symbols
 
2152
     */
 
2153
    sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
 
2154
    if (!sym)
 
2155
    {
 
2156
        sym = (GlobalSymbol*)malloc(sizeof(*sym));
 
2157
        sym->tag = tag_str;
 
2158
        sym->ps = NULL;
 
2159
        INIT_LIST_HEAD(&sym->tags);
 
2160
 
 
2161
        put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
 
2162
    }
 
2163
 
 
2164
    tag = (Tag*)malloc(sizeof(*tag));
 
2165
    tag->tag = tag_str;
 
2166
    tag->rev = rev;
 
2167
    tag->sym = sym;
 
2168
    list_add(&tag->global_link, &sym->tags);
 
2169
    list_add(&tag->rev_link, &rev->tags);
 
2170
}
 
2171
 
 
2172
char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
 
2173
{
 
2174
    char * new_tag;
 
2175
    char * new_rev;
 
2176
 
 
2177
    if (get_hash_object(file->branches, rev))
 
2178
    {
 
2179
        debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s", 
 
2180
              rev, tag, file->filename);
 
2181
        return NULL;
 
2182
    }
 
2183
 
 
2184
    /* get permanent storage for the strings */
 
2185
    new_tag = get_string(tag);
 
2186
    new_rev = get_string(rev); 
 
2187
 
 
2188
    put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
 
2189
    put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
 
2190
    
 
2191
    return new_tag;
 
2192
}
 
2193
 
 
2194
/*
 
2195
 * Resolve each global symbol to a PatchSet.  This is
 
2196
 * not necessarily doable, because tagging isn't 
 
2197
 * necessarily done to the project as a whole, and
 
2198
 * it's possible that no tag is valid for all files 
 
2199
 * at a single point in time.  We check for that
 
2200
 * case though.
 
2201
 *
 
2202
 * Implementation: the most recent PatchSet containing
 
2203
 * a revision (post_rev) tagged by the symbol is considered
 
2204
 * the 'tagged' PatchSet.
 
2205
 */
 
2206
 
 
2207
static void resolve_global_symbols()
 
2208
{
 
2209
    struct hash_entry * he_sym;
 
2210
    reset_hash_iterator(global_symbols);
 
2211
    while ((he_sym = next_hash_entry(global_symbols)))
 
2212
    {
 
2213
        GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
 
2214
        PatchSet * ps;
 
2215
        struct list_head * next;
 
2216
 
 
2217
        debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
 
2218
 
 
2219
        /*
 
2220
         * First pass, determine the most recent PatchSet with a 
 
2221
         * revision tagged with the symbolic tag.  This is 'the'
 
2222
         * patchset with the tag
 
2223
         */
 
2224
 
 
2225
        for (next = sym->tags.next; next != &sym->tags; next = next->next)
 
2226
        {
 
2227
            Tag * tag = list_entry(next, Tag, global_link);
 
2228
            CvsFileRevision * rev = tag->rev;
 
2229
 
 
2230
            /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
 
2231
            if (!rev->present || !rev->post_psm)
 
2232
            {
 
2233
                struct list_head *tmp = next->prev;
 
2234
                debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
 
2235
                      rev->rev, rev->file->filename);
 
2236
                /* FIXME: memleak */
 
2237
                list_del(next);
 
2238
                next = tmp;
 
2239
                continue;
 
2240
            }
 
2241
 
 
2242
            ps = rev->post_psm->ps;
 
2243
 
 
2244
            if (!sym->ps || ps->date > sym->ps->date)
 
2245
                sym->ps = ps;
 
2246
        }
 
2247
        
 
2248
        /* convenience variable */
 
2249
        ps = sym->ps;
 
2250
 
 
2251
        if (!ps)
 
2252
        {
 
2253
            debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
 
2254
            return;
 
2255
        }
 
2256
 
 
2257
        ps->tag = sym->tag;
 
2258
 
 
2259
        /* check if this ps is one of the '-r' patchsets */
 
2260
        if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
 
2261
            restrict_tag_ps_start = ps->psid;
 
2262
 
 
2263
        /* the second -r implies -b */
 
2264
        if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
 
2265
        {
 
2266
            restrict_tag_ps_end = ps->psid;
 
2267
 
 
2268
            if (restrict_branch)
 
2269
            {
 
2270
                if (strcmp(ps->branch, restrict_branch) != 0)
 
2271
                {
 
2272
                    debug(DEBUG_APPMSG1, 
 
2273
                          "WARNING: -b option and second -r have conflicting branches: %s %s", 
 
2274
                          restrict_branch, ps->branch);
 
2275
                }
 
2276
            }
 
2277
            else
 
2278
            {
 
2279
                debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
 
2280
                restrict_branch = ps->branch;
 
2281
            }
 
2282
        }
 
2283
 
 
2284
        /* 
 
2285
         * Second pass. 
 
2286
         * check if this is an invalid patchset, 
 
2287
         * check which members are invalid.  determine
 
2288
         * the funk factor etc.
 
2289
         */
 
2290
        for (next = sym->tags.next; next != &sym->tags; next = next->next)
 
2291
        {
 
2292
            Tag * tag = list_entry(next, Tag, global_link);
 
2293
            CvsFileRevision * rev = tag->rev;
 
2294
            CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
 
2295
            
 
2296
            if (!next_rev)
 
2297
                continue;
 
2298
                
 
2299
            /*
 
2300
             * we want the 'tagged revision' to be valid until after
 
2301
             * the date of the 'tagged patchset' or else there's something
 
2302
             * funky going on
 
2303
             */
 
2304
            if (next_rev->post_psm->ps->date < ps->date)
 
2305
            {
 
2306
                int flag = check_rev_funk(ps, next_rev);
 
2307
                debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
 
2308
                      rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
 
2309
                ps->tag_flags |= flag;
 
2310
            }
 
2311
        }
 
2312
    }
 
2313
}
 
2314
 
 
2315
static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
 
2316
{
 
2317
    /* special case the branch called 'HEAD' */
 
2318
    if (strcmp(branch, "HEAD") == 0)
 
2319
    {
 
2320
        /* look for only one '.' in rev */
 
2321
        char * p = strchr(rev->rev, '.');
 
2322
        if (p && !strchr(p + 1, '.'))
 
2323
            return 1;
 
2324
    }
 
2325
    else
 
2326
    {
 
2327
        char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
 
2328
        
 
2329
        if (branch_rev)
 
2330
        {
 
2331
            char post_rev[REV_STR_MAX];
 
2332
            char branch[REV_STR_MAX];
 
2333
            int file_leaf, branch_leaf;
 
2334
            
 
2335
            strcpy(branch, branch_rev);
 
2336
            
 
2337
            /* first get the branch the file rev is on */
 
2338
            if (get_branch_ext(post_rev, rev->rev, &file_leaf))
 
2339
            {
 
2340
                branch_leaf = file_leaf;
 
2341
                
 
2342
                /* check against branch and all branch ancestor branches */
 
2343
                do 
 
2344
                {
 
2345
                    debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
 
2346
                    if (strcmp(branch, post_rev) == 0)
 
2347
                        return (file_leaf <= branch_leaf);
 
2348
                }
 
2349
                while(get_branch_ext(branch, branch, &branch_leaf));
 
2350
            }
 
2351
        }
 
2352
    }
 
2353
 
 
2354
    return 0;
 
2355
}
 
2356
 
 
2357
static int count_dots(const char * p)
 
2358
{
 
2359
    int dots = 0;
 
2360
 
 
2361
    while (*p)
 
2362
        if (*p++ == '.')
 
2363
            dots++;
 
2364
 
 
2365
    return dots;
 
2366
}
 
2367
 
 
2368
/*
 
2369
 * When importing vendor sources, (apparently people do this)
 
2370
 * the code is added on a 'vendor' branch, which, for some reason
 
2371
 * doesn't use the magic-branch-tag format.  Try to detect that now
 
2372
 */
 
2373
static int is_vendor_branch(const char * rev)
 
2374
{
 
2375
    return !(count_dots(rev)&1);
 
2376
}
 
2377
 
 
2378
void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
 
2379
{
 
2380
    /* check if a member for the same file already exists, if so
 
2381
     * put this PatchSet on the collisions list 
 
2382
     */
 
2383
    struct list_head * next;
 
2384
    for (next = ps->members.next; next != &ps->members; next = next->next) 
 
2385
    {
 
2386
        PatchSetMember * m = list_entry(next, PatchSetMember, link);
 
2387
        if (m->file == psm->file && ps->collision_link.next == NULL) 
 
2388
                list_add(&ps->collision_link, &collisions);
 
2389
    }
 
2390
 
 
2391
    psm->ps = ps;
 
2392
    list_add(&psm->link, ps->members.prev);
 
2393
}
 
2394
 
 
2395
static void set_psm_initial(PatchSetMember * psm)
 
2396
{
 
2397
    psm->pre_rev = NULL;
 
2398
    if (psm->post_rev->dead)
 
2399
    {
 
2400
        /* 
 
2401
         * we expect a 'file xyz initially added on branch abc' here
 
2402
         * but there can only be one such member in a given patchset
 
2403
         */
 
2404
        if (psm->ps->branch_add)
 
2405
            debug(DEBUG_APPMSG1, "WARNING: branch_add already set!");
 
2406
        psm->ps->branch_add = 1;
 
2407
    }
 
2408
}
 
2409
 
 
2410
/* 
 
2411
 * look at all revisions starting at rev and going forward until 
 
2412
 * ps->date and see whether they are invalid or just funky.
 
2413
 */
 
2414
static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
 
2415
{
 
2416
    int retval = TAG_FUNKY;
 
2417
 
 
2418
    while (rev)
 
2419
    {
 
2420
        PatchSet * next_ps = rev->post_psm->ps;
 
2421
        struct list_head * next;
 
2422
 
 
2423
        if (next_ps->date > ps->date)
 
2424
            break;
 
2425
 
 
2426
        debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s", 
 
2427
              ps->date, next_ps->date, rev->rev, rev->branch);
 
2428
 
 
2429
        /*
 
2430
         * If the ps->tag is one of the two possible '-r' tags
 
2431
         * then the funkyness is even more important.
 
2432
         *
 
2433
         * In the restrict_tag_start case, this next_ps is chronologically
 
2434
         * before ps, but tagwise after, so set the funk_factor so it will
 
2435
         * be included.
 
2436
         *
 
2437
         * The restrict_tag_end case is similar, but backwards.
 
2438
         *
 
2439
         * Start assuming the HIDE/SHOW_ALL case, we will determine
 
2440
         * below if we have a split ps case 
 
2441
         */
 
2442
        if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
 
2443
            next_ps->funk_factor = FNK_SHOW_ALL;
 
2444
        if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
 
2445
            next_ps->funk_factor = FNK_HIDE_ALL;
 
2446
 
 
2447
        /*
 
2448
         * if all of the other members of this patchset are also 'after' the tag
 
2449
         * then this is a 'funky' patchset w.r.t. the tag.  however, if some are
 
2450
         * before then the patchset is 'invalid' w.r.t. the tag, and we mark
 
2451
         * the members individually with 'bad_funk' ,if this tag is the
 
2452
         * '-r' tag.  Then we can actually split the diff on this patchset
 
2453
         */
 
2454
        for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
 
2455
        {
 
2456
            PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
2457
            if (before_tag(psm->post_rev, ps->tag))
 
2458
            {
 
2459
                retval = TAG_INVALID;
 
2460
                /* only set bad_funk for one of the -r tags */
 
2461
                if (next_ps->funk_factor)
 
2462
                {
 
2463
                    psm->bad_funk = 1;
 
2464
                    next_ps->funk_factor = 
 
2465
                        (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
 
2466
                }
 
2467
                debug(DEBUG_APPMSG1, 
 
2468
                      "WARNING: Invalid PatchSet %d, Tag %s:\n"
 
2469
                      "    %s:%s=after, %s:%s=before. Treated as 'before'", 
 
2470
                      next_ps->psid, ps->tag, 
 
2471
                      rev->file->filename, rev->rev, 
 
2472
                      psm->post_rev->file->filename, psm->post_rev->rev);
 
2473
            }
 
2474
        }
 
2475
 
 
2476
        rev = rev_follow_branch(rev, ps->branch);
 
2477
    }
 
2478
 
 
2479
    return retval;
 
2480
}
 
2481
 
 
2482
/* determine if the revision is before the tag */
 
2483
static int before_tag(CvsFileRevision * rev, const char * tag)
 
2484
{
 
2485
    CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
 
2486
    int retval = 0;
 
2487
 
 
2488
    if (tagged_rev && 
 
2489
        revision_affects_branch(rev, tagged_rev->branch) && 
 
2490
        rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
 
2491
        retval = 1;
 
2492
 
 
2493
    debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d", 
 
2494
          rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
 
2495
 
 
2496
    return retval;
 
2497
}
 
2498
 
 
2499
/* get the next revision from this one following branch if possible */
 
2500
/* FIXME: not sure if this needs to follow branches leading up to branches? */
 
2501
static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
 
2502
{
 
2503
    struct list_head * next;
 
2504
 
 
2505
    /* check for 'main line of inheritance' */
 
2506
    if (strcmp(rev->branch, branch) == 0)
 
2507
        return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
 
2508
 
 
2509
    /* look down branches */
 
2510
    for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
 
2511
    {
 
2512
        CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
 
2513
        //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
 
2514
        if (strcmp(next_rev->branch, branch) == 0)
 
2515
            return next_rev;
 
2516
    }
 
2517
    
 
2518
    return NULL;
 
2519
}
 
2520
 
 
2521
static void check_norc(int argc, char * argv[])
 
2522
{
 
2523
    int i = 1; 
 
2524
    while (i < argc)
 
2525
    {
 
2526
        if (strcmp(argv[i], "--norc") == 0)
 
2527
        {
 
2528
            norc = "-f";
 
2529
            break;
 
2530
        }
 
2531
        i++;
 
2532
    }
 
2533
}
 
2534
 
 
2535
static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
 
2536
{
 
2537
    struct list_head * next;
 
2538
    CvsFileRevision * rev;
 
2539
 
 
2540
    /* PatchSet 1 has no ancestor */
 
2541
    if (ps->psid == 1)
 
2542
        return;
 
2543
 
 
2544
    /* HEAD branch patchsets have no ancestry, but callers should know that */
 
2545
    if (strcmp(ps->branch, "HEAD") == 0)
 
2546
    {
 
2547
        debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
 
2548
        return;
 
2549
    }
 
2550
 
 
2551
    for (next = ps->members.next; next != &ps->members; next = next->next) 
 
2552
    {
 
2553
        PatchSetMember * psm = list_entry(next, PatchSetMember, link);
 
2554
        rev = psm->pre_rev;
 
2555
        int d1, d2;
 
2556
 
 
2557
        /* the reason this is at all complicated has to do with a 
 
2558
         * branch off of a branch.  it is possible (and indeed 
 
2559
         * likely) that some file would not have been modified 
 
2560
         * from the initial branch point to the branch-off-branch 
 
2561
         * point, and therefore the branch-off-branch point is 
 
2562
         * really branch-off-HEAD for that specific member (file).  
 
2563
         * in that case, rev->branch will say HEAD but we want 
 
2564
         * to know the symbolic name of the first branch
 
2565
         * so we continue to look member after member until we find
 
2566
         * the 'deepest' branching.  deepest can actually be determined
 
2567
         * by considering the revision currently indicated by 
 
2568
         * ps->ancestor_branch (by symbolic lookup) and rev->rev. the 
 
2569
         * one with more dots wins
 
2570
         *
 
2571
         * also, the first commit in which a branch-off-branch is 
 
2572
         * mentioned may ONLY modify files never committed since
 
2573
         * original branch-off-HEAD was created, so we have to keep
 
2574
         * checking, ps after ps to be sure to get the deepest ancestor
 
2575
         *
 
2576
         * note: rev is the pre-commit revision, not the post-commit
 
2577
         */
 
2578
        if (!head_ps->ancestor_branch)
 
2579
            d1 = 0;
 
2580
        else if (strcmp(ps->branch, rev->branch) == 0)
 
2581
            continue;
 
2582
        else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
 
2583
            d1 = 1;
 
2584
        else {
 
2585
            /* branch_rev may not exist if the file was added on this branch for example */
 
2586
            const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
 
2587
            d1 = branch_rev ? count_dots(branch_rev) : 1;
 
2588
        }
 
2589
        
 
2590
        /* HACK: we sometimes pretend to derive from the import branch.  
 
2591
         * just don't do that.  this is the easiest way to prevent... 
 
2592
         */
 
2593
        d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
 
2594
        
 
2595
        if (d2 > d1)
 
2596
            head_ps->ancestor_branch = rev->branch;
 
2597
 
 
2598
        //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
 
2599
    }
 
2600
}
 
2601
 
 
2602
static void handle_collisions()
 
2603
{
 
2604
    struct list_head *next;
 
2605
    for (next = collisions.next; next != &collisions; next = next->next) 
 
2606
    {
 
2607
        PatchSet * ps = list_entry(next, PatchSet, collision_link);
 
2608
        printf("PatchSet %d has collisions\n", ps->psid);
 
2609
    }
 
2610
}
 
2611
 
 
2612
void walk_all_patch_sets(void (*action)(PatchSet *))
 
2613
{
 
2614
    struct list_head * next;;
 
2615
    for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
 
2616
        PatchSet * ps = list_entry(next, PatchSet, all_link);
 
2617
        action(ps);
 
2618
    }
 
2619
}