It’s pretty easy to say that there’s just one branch for the FreeBSD ports tree. And it’s not really a branch at all. Commits were done to HEAD, and branches were ignored. That has recently changed. The FreeBSD ports tree will be branched every three months and commits to that branch will be tightly controlled. HEAD will continue along as it has in the past.
System background
Before I get to how we need to change FreshPorts, let’s see what it is doing now.
The FreshPorts website was designed with HEAD in mind. However, the FreshPorts database was designed with branches in mind. FreshPorts currently tracks 124 different branches:
freshports.org=# \d system_branch
Table "public.system_branch"
Column | Type | Modifiers
-------------+---------+------------------------------------------------------------
id | integer | not null default nextval('system_branch_id_seq'::regclass)
system_id | integer | not null
branch_name | text |
Indexes:
"system_branch_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"$1" FOREIGN KEY (system_id) REFERENCES system(id) ON UPDATE CASCADE ON DELETE CASCADE
Referenced by:
TABLE "system_branch_element_revision" CONSTRAINT "$2" FOREIGN KEY (system_branch_id) REFERENCES system_branch(id) ON UPDATE CASCADE ON DELETE CASCADE
freshports.org=# select * from system_branch order by 2;
id | system_id | branch_name
-----+-----------+----------------------------------
1 | 1 | HEAD
2 | 1 | RELENG_4
3 | 1 | ZLIB
4 | 1 | NETGRAPH
5 | 1 | LUKEM
6 | 1 | OPENPAM
7 | 1 | INTEL
8 | 1 | BELL_LABS
9 | 1 | LWALL
10 | 1 | NETBSD
11 | 1 | RELENG_3
12 | 1 | OpenBSD
13 | 1 | DARRENR
14 | 1 | FSF
15 | 1 | NRL
16 | 1 | RELENG_4_5
17 | 1 | RELENG_4_4
18 | 1 | RELENG_4_3
19 | 1 | ISC
20 | 1 | 1.1.1
21 | 1 | SOLAR_DESIGNER
22 | 1 | ADO
23 | 1 | WIP_GCC31
24 | 1 | SENDMAIL
25 | 1 | KNU
26 | 1 | HEAD,RELENG_4
27 | 1 | OPENBSD
28 | 1 | MAIN
29 | 1 | BP
30 | 1 | FREEBSD
31 | 1 | NCURSES
32 | 1 | RELENG_4_6
33 | 1 | TCPDUMP_ORG
34 | 1 | OPENSSH
35 | 1 | NetBSD
36 | 1 | CVSWEB
37 | 1 | RELENG_2_2
38 | 1 | 1
39 | 1 | JPO
40 | 1 | ZOULAS
41 | 1 | LBL
42 | 1 | OPENSSL
43 | 1 | rel-2_0-branch
44 | 1 | RELENG_4_4,RELENG_4_5,RELENG_4_6
45 | 1 | KTH
46 | 1 | CVSHOME
47 | 1 | EXPAT
48 | 1 | RELENG_4_7
49 | 1 | TEXFM
50 | 1 | maho
51 | 1 | DMILLS
52 | 1 | KORTY
53 | 1 | RELENG_5_0
54 | 1 | gmt
55 | 1 | LINUX
56 | 1 | davidxu
57 | 1 | NETLIB
58 | 1 | RELENG_4_8
59 | 1 | CREATIVE
60 | 1 | LIBUWX
61 | 1 | COLUMBIA
62 | 1 | RELENG_5_1
63 | 1 | ALSA
64 | 1 | Atheros
65 | 1 | GNU
66 | 1 | LIBTHR_ALPHA_INIT
67 | 1 | ATHEROS
68 | 1 | SNERT
69 | 1 | BEGEMOT
70 | 1 | RELENG_4,RELENG_4_8
71 | 1 | RELENG_4_9
72 | 1 | RELENG_3,RELENG_4
73 | 1 | DARRENREED
74 | 1 | RELENG_5_2
75 | 1 | LINUX_TEST_PROJECT
76 | 1 | OPEN_POSIX_TESTSUITE
77 | 1 | MTM_LOCAL
78 | 1 | CYCLIC
79 | 1 | VJS
80 | 1 | LESS
81 | 1 | RELENG_4_10
82 | 1 | JOERG
83 | 1 | KAME
84 | 1 | RELENG_5
85 | 1 | HEAD,RELENG_5
86 | 1 | RELENG_5_3
87 | 1 | RELENG_4_11
88 | 1 | NVIDIA
89 | 1 | RELENG_5_4
90 | 1 | netbsd
91 | 1 | BZIP2
92 | 1 | MALINEN
93 | 1 | RELENG_6
94 | 1 | RELENG_6_0
95 | 1 | TrustedBSD
96 | 1 | CSUP
97 | 1 | RELENG_6_1
98 | 1 | RELENG_5_5
99 | 1 | bsd
100 | 1 | ATHEROS,HEAD
101 | 1 | FSF,HEAD
102 | 1 | mackerras
103 | 1 | RELENG_6_2
104 | 1 | GSF
105 | 1 | FreeBSD
106 | 1 | RELENG_7
107 | 1 | RELENG_6_3
108 | 1 | RELENG_7_0
109 | 1 | HEAD,INTEL
110 | 1 | 2.0
111 | 1 | SUN
112 | 1 | RELENG_6_4
113 | 1 | RELENG_7_1
114 | 1 | RELENG_7_2
115 | 1 | RELENG_8
116 | 1 | RELENG_8_0
117 | 1 | RELENG_7_3
118 | 1 | RELENG_8_1
119 | 1 | RELENG_8_2
120 | 1 | RELENG_7_4
121 | 1 | UNKNOWN
122 | 1 | VENDOR
123 | 1 | RELENG_9
124 | 1 | RELENG_10
(124 rows)
freshports.org=#
You will notice that some commits are to multiple branches. e.g. HEAD,RELENG_4. That should be cleaned up so the system puts that incoming commit into two different branches, not one.
Why are all the system_id values set to 1? That is easily answered:
freshports.org=# \d system
Table "public.system"
Column | Type | Modifiers
-------------+----------+-----------------------------------------------------
id | integer | not null default nextval('system_id_seq'::regclass)
name | text | not null
time_adjust | interval | not null default '00:00:00'::interval
Indexes:
"system_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "commit_log" CONSTRAINT "$1" FOREIGN KEY (system_id) REFERENCES system(id) ON UPDATE CASCADE ON DELETE CASCADE
TABLE "system_branch" CONSTRAINT "$1" FOREIGN KEY (system_id) REFERENCES system(id) ON UPDATE CASCADE ON DELETE CASCADE
freshports.org=# select * from system;
id | name | time_adjust
----+---------+-------------
1 | FreeBSD | 00:00:00
(1 row)
freshports.org=#
The database is designed to hold commits from various systems. Only FreeBSD has been added so far.
So, FreshPorts is tracking branches for all commits to the FreeBSD repo, not just ports.
Testing the branch theory
To see if I could backtrack from a branch to the commits on the branch, I started with a recent commit to sysutils/zfs-periodic (a port I hope to make use of very soon now). Let’s start with this query:
freshports.org=# SELECT CL.id FROM commit_log CL WHERE CL.message_id = E'201312271836.rBRIaoHN063395@svn.freebsd.org'; id -------- 500046 (1 row) freshports.org=#
That’s the unique id for this commit. What files were touched by that commit?
freshports.org=# SELECT CL.id, CLE.*, element_pathname(element_id) FROM commit_log CL, commit_log_elements CLE WHERE CL.message_id = E'201312271836.rBRIaoHN063395@svn.freebsd.org' AND CLE.commit_log_id = CL.id; id | id | commit_log_id | element_id | revision_name | change_type | element_pathname --------+---------+---------------+------------+---------------+-------------+-------------------------------------------- 500046 | 2269113 | 500046 | 359439 | 337778 | M | /ports/head/sysutils/zfs-periodic/Makefile (1 row) freshports.org=#
OK. Let’s connect into the system_branch_element_revision table and see what references we find:
freshports.org=# SELECT CL.id, CLE.*, element_pathname(CLE.element_id)
FROM commit_log CL, commit_log_elements CLE, system_branch_element_revision SBER
WHERE CL.message_id = E'201312271836.rBRIaoHN063395@svn.freebsd.org'
AND CLE.commit_log_id = CL.id
AND CLE.element_id = SBER.element_id
AND CLE.revision_name = SBER.revision_name;
id | id | commit_log_id | element_id | revision_name | change_type | element_pathname
----+----+---------------+------------+---------------+-------------+------------------
(0 rows)
freshports.org=#
Nothing. Hmmm. That’s bad.
At this point, I start playing with various queries to see how many references I could find to the element_id in question.
freshports.org=# select *
from system_branch_element_revision SBER
WHERE 359439 = SBER.element_id;
system_branch_id | element_id | revision_name
------------------+------------+---------------
1 | 359439 | 1.1
121 | 359439 | 301858
(2 rows)
freshports.org=# select * from commit_log_elements CLE
WHERE 359439 = CLE.element_id;
id | commit_log_id | element_id | revision_name | change_type
---------+---------------+------------+---------------+-------------
2269113 | 500046 | 359439 | 337778 | M
2190381 | 486227 | 359439 | 327772 | M
1611161 | 387853 | 359439 | 1.1 | A
1790229 | 421143 | 359439 | 1.2 | M
1917252 | 443752 | 359439 | 301858 | M
1987472 | 455607 | 359439 | 308502 | M
2018932 | 462046 | 359439 | 312371 | M
(7 rows)
freshports.org=#
There are 7 commits to the Makefile, but only two are recorded in the system_branch_element_revision. That’s not good. We want them all recorded.
Where is it going wrong?
I searched the code for references to system_branch_element_revision and found this stored procedure:
CREATE OR REPLACE FUNCTION ElementTagSet(int4, int4, text) RETURNS boolean AS $$
DECLARE
SystemBranchID ALIAS for $1;
ElementID ALIAS for $2;
RevisionName ALIAS for $3;
ExistingRevisionFound text;
BEGIN
-- has this element already been tagged with this version?
ExistingRevisionFound := RevisionForTagGet(SystemBranchID, ElementID);
IF ExistingRevisionFound IS NULL THEN
insert into system_branch_element_revision
(system_branch_id, element_id, revision_name)
values (SystemBranchID, ElementID, RevisionName);
END IF;
return 1;
END;
$$
LANGUAGE plpgsql;
I also found log output which showed that the example commit executed this SQL (note, the previous queries were on the production database; the following queries are on the test database; thus, the element_id in question may vary):
sql = 'select ElementTagSet(1, 359439, '337778')'
Let’s see what the above stored procedure does with that input.
freshports.org=# begin; BEGIN freshports.org=# select ElementTagSet(1, 359439, '337778'); elementtagset --------------- t (1 row) freshports.org=# select * from system_branch_element_revision where revision_name = '337778'; system_branch_id | element_id | revision_name ------------------+------------+--------------- (0 rows) freshports.org=# rollback; ROLLBACK freshports.org=#
Clearly, that’s not working as designed.
Digging deeper
Let’s see what RevisionForTagGet() returns. This function is straight forward:
CREATE OR REPLACE FUNCTION RevisionForTagGet(int4, int4 ) RETURNS text AS $$
DECLARE
SystemBranchID ALIAS for $1;
ElementID ALIAS for $2;
TempRevisionName text;
-- for a given Element and TagName (System version id)
-- return the revision name assocated with it.
BEGIN
select revision_name
into TempRevisionName
from system_branch_element_revision
where system_branch_id = SystemBranchID
and element_id = ElementID;
return TempRevisionName;
END;
$$ LANGUAGE 'plpgsql';
freshports.org=# select RevisionForTagGet(1, 359439);
revisionfortagget
-------------------
1.1
(1 row)
freshports.org=#
Looking at ElementTagSet() on line 14, I think that’s the key. What we need to do is an insert, and that insert is not happening. I think the cause is the ElementTagSet() function, which needs to do an insert if the tuple is not already in the table. That probably means that RevisionForTagGet() is no longer required. I think the fix is pretty straight forward, but I’ve run out of time today.











