File: branch.lisp

package info (click to toggle)
mcvs 1.0.13-8
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 668 kB
  • ctags: 648
  • sloc: lisp: 5,091; ansic: 223; sh: 190; makefile: 58
file content (329 lines) | stat: -rw-r--r-- 12,594 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
;;; This source file is part of the Meta-CVS program, 
;;; which is distributed under the GNU license.
;;; Copyright 2002 Kaz Kylheku

(require "split")
(require "mapping")
(require "options")
(require "update")
(require "system")

(defconstant *branch-char* #\~)

(defun tags-from-cvs-log (stream)
"Parse stream which is assumed to be the output of a cvs log -h command
for a single file. Return two associative lists, one of version tags
and one of branch tags."
  (let (syms (state :initial))
    (loop
      (let ((line (read-line stream nil)))
	(when (null line)
          (return-from tags-from-cvs-log (nreverse syms)))
	(ecase state
	  ((:initial)
	    (if (string= line "symbolic names:")
	      (setf state :syms)))
	  ((:syms)
	    (cond 
	      ((and (not (zerop (length line)))
		    (char= (char line 0) #\tab))
		 (push (split-words line #(#\: #\space #\tab)) syms))
	      (t (setf state :final))))
	  ((:final)))))))

(defun parse-dir-sticky (sticky)
  (if (string= "" sticky)
    nil
    (let ((first-char (char sticky 0))
	  (rest-string (substring sticky 1)))
      (case first-char
	(#\T (list :branch rest-string))
	(#\D (list :date rest-string))
	(#\N (list :version rest-string))
	(otherwise (list :other sticky))))))

(defun parse-entries-sticky (sticky)
  (if (string= "" sticky)
    nil
    (let ((first-char (char sticky 0))
	  (rest-string (substring sticky 1)))
      (case first-char
	(#\T (list :tag rest-string))
	(#\D (list :date rest-string))
	(otherwise (list :other sticky))))))

(defun equal-sticky (left right)
  (cond
    ((eq left right) t)
    ((null left) nil)
    ((null right) nil)
    (t (destructuring-bind (type-left text-left) left
	 (destructuring-bind (type-right text-right) right
	   (and (equal text-left text-right)
		(or (eq type-left type-right)
		    (and (eq type-left :tag)
			 (member type-right '(:version :branch)))
		    (and (eq type-right :tag)
			 (member type-left '(:version :branch))))))))))

(defun read-cvs-entries ()
  (with-open-file (f "CVS/Entries" :direction :input :if-does-not-exist nil)
    (when (not f)
      (error "cannot read CVS/Entries"))
    (let (entries)
      (do ((line (read-line f nil) (read-line f nil)))
	  ((null line) (nreverse entries))
	  (let ((split (split-fields line #(#\/))))
	    (setf (first split)
		  (cond 
		    ((string= "" (first split)) :file)
		    ((string= "D" (first split)) :directory)
		    (t :other)))
	    (when (sixth split)
	      (setf (sixth split) (parse-entries-sticky (sixth split))))
	    (push split entries))))))

(defun same-tag-check (entries &optional directory-sticky-tag)
  (let ((file-entries (remove-if-not #'(lambda (x) (eq x :file))
				     entries
				     :key #'first)))
    (let ((first-tag (or directory-sticky-tag (sixth (first file-entries)))))
      (not (find-if-not #'(lambda (x) (equal-sticky x first-tag))
			file-entries :key #'sixth)))))

(defun what-are-we-sticky-to ()
  (with-open-file (f "CVS/Tag" :direction :input :if-does-not-exist nil)
    (if f
      (let ((contents (read-line f nil)))
	(if contents
	  (parse-dir-sticky contents))))))

(defun what-module-is-this ()
  (with-open-file (f "CVS/Repository" :direction :input)
    (read-line f)))

(defun where-is-the-repository ()
  (with-open-file (f "CVS/Root" :direction :input)
    (read-line f)))

(defun branch-tag-check (tag)
  (when (some #'(lambda (ch) (char= ch *branch-char*)) tag)
    (error "tag must not contain ~a character." *branch-char*))
  (when (string= tag "HEAD")
    (error "HEAD is a reserved symbol." *branch-char*)))

(defun mcvs-branch (global-options branch-name)
  (branch-tag-check branch-name)
  (in-sandbox-root-dir
    (let ((branchpoint-tag (format nil "~a~abranch-point" 
				   branch-name *branch-char*)))
      (chdir *mcvs-dir*)
      (chatter-debug "Invoking CVS.~%")
      (execute-program `("cvs" ,@(format-opt global-options) 
			 "tag" "-b" ,branch-name))
      (execute-program `("cvs" ,@(format-opt global-options) 
			 "tag" ,branchpoint-tag)))))

(defun mcvs-branch-wrapper (global-options command-options command-args)
  (declare (ignore command-options))
  (if (/= (length command-args) 1)
    (error "specify branch symbol")
  (mcvs-branch global-options (first command-args))))

(defun cvs-make-or-advance-tag (global-options tag &optional tag-what)
  (let ((module (what-module-is-this))
	(repo (where-is-the-repository)))
    (if (or (not (execute-program `("cvs" ,@(format-opt global-options)
				    "tag" "-d" ,tag ,*mcvs-map-name*)))
	    (not (execute-program `("cvs" ,@(format-opt global-options) 
				    "-d" ,repo "rtag" "-F"
				    ,@(if tag-what `("-r" ,tag-what))
				    ,tag ,module))))
      (error "CVS tagging operation failed."))))

(defun mcvs-merge (global-options command-options branch-name &key remerge-p)
  (branch-tag-check branch-name)
  (in-sandbox-root-dir
    (chdir *mcvs-dir*)
    (let ((branchpoint-tag (format nil "~a~abranch-point" 
				   branch-name *branch-char*))
	  (current-sticky (what-are-we-sticky-to))
	  this-branch
	  (symbols (with-input-from-program (s `("cvs" 
						 ,@(format-opt global-options)
						 "log" "-h" ,*mcvs-map-name*))
		     (tags-from-cvs-log s))))

      (when (not (or (null current-sticky) 
		     (eq (first current-sticky) :branch)))
	(error "working copy is currently updated to a non-branch tag."))

      (setf this-branch (or (second current-sticky) "HEAD"))

      (when (string= this-branch branch-name)
	(error "cannot merge branch to itself."))

      (let* ((even-merge-tag (format nil "~a~amerged-to-~a-0" branch-name 
				     *branch-char* this-branch))
	     (odd-merge-tag (format nil "~a~amerged-to-~a-1" branch-name 
				    *branch-char* this-branch))
	     (branch-tag-pos (position branch-name symbols
				       :key #'first :test #'string=))
	     (even-tag-pos (position even-merge-tag symbols 
				     :key #'first :test #'string=))
	     (odd-tag-pos (position odd-merge-tag symbols 
				    :key #'first :test #'string=))
	     (bp-tag-pos (position branchpoint-tag symbols
				   :key #'first :test #'string=))
	     from-tag to-tag)

	(when (not branch-tag-pos)
	  (error "unable to retrieve branch symbol ~a." branch-name))
	(when (not bp-tag-pos)
	  (error "this is not a Meta-CVS managed branch."))

	(cond
	  (remerge-p
	     (cond
	       ((and even-tag-pos odd-tag-pos) 
		  (if (< even-tag-pos odd-tag-pos)
		    (setf from-tag odd-merge-tag to-tag even-merge-tag)
		    (setf from-tag even-merge-tag to-tag odd-merge-tag)))
	       (odd-tag-pos
		  (setf from-tag branchpoint-tag to-tag odd-merge-tag))
	       (even-tag-pos
		  (setf from-tag branchpoint-tag to-tag even-merge-tag))
	       (t (error "no prior merge was done"))))
	  (t (cond
	       ((and even-tag-pos odd-tag-pos)
		  (if (< even-tag-pos odd-tag-pos)
		    (setf from-tag even-merge-tag to-tag odd-merge-tag)
		    (setf from-tag odd-merge-tag to-tag even-merge-tag)))
	       (even-tag-pos
		  (setf from-tag even-merge-tag to-tag odd-merge-tag))
	       (odd-tag-pos
		  (setf from-tag odd-merge-tag to-tag even-merge-tag))
	       (t (setf from-tag branchpoint-tag to-tag even-merge-tag)))
	     (cvs-make-or-advance-tag global-options to-tag branch-name)))
	(mcvs-update global-options `(("j" ,from-tag) ("j" ,to-tag)
                                  ,@command-options))))))

(defun mcvs-list-branches (global-options)
  (in-sandbox-root-dir
    (chdir *mcvs-dir*)
    (let ((symbols (with-input-from-program (s `("cvs" 
						 ,@(format-opt global-options)
						 "log" "-h" ,*mcvs-map-name*))
		      (tags-from-cvs-log s)))
	  (entries (read-cvs-entries))
	  (branchpoint-suffix (format nil "~abranch-point" *branch-char*))
	  (current-sticky (what-are-we-sticky-to)))

      (format t "currently on: ~a (~a)~%" 
	      (or (second current-sticky) "main trunk")
	      (case (first current-sticky)
		((:branch)
		   (if (find (format nil "~a~abranch-point"
				     (second current-sticky) *branch-char*) 
			     symbols :key #'first :test #'string=)
		     "managed branch"
		     "non-managed branch"))
		((:version)
		   "version tag")
		((:date)
		   "sticky date")
		((nil) "no sticky tag")))

      (when (not (same-tag-check entries current-sticky))
	(format t "warning: one or more files not on ~a~%" 
		(or (second current-sticky) "main trunk")))

      (format t "branch list: ~%")
      (dolist (symbol symbols)
	(let* ((tag (first symbol))
	       (offset (search branchpoint-suffix tag)))
	  (when (and offset
		     (> offset 0)
		     (= offset (- (length tag) (length branchpoint-suffix))))
	    (format t "~a~a~%" #\Tab (substring tag 0 offset))))))))

(defun mcvs-merge-wrapper (global-options command-options command-args)
  (when (/= (length command-args) 1)
    (error "specify source branch symbol."))
  (mcvs-merge global-options command-options (first command-args)))

(defun mcvs-remerge-wrapper (global-options command-options command-args)
  (when (/= (length command-args) 1)
    (error "specify source branch symbol."))
  (mcvs-merge global-options command-options (first command-args) :remerge-p t))

(defun mcvs-list-branches-wrapper (global-options command-options command-args)
  (declare (ignore command-options))
  (when (not (zerop (length command-args)))
    (error "command takes no arguments."))
  (mcvs-list-branches global-options))

(defun mcvs-switch-wrapper (global-options command-options command-args)
  (let ((up-opt (case (length command-args)
		  ((0) `("A"))
		  ((1) `("r" ,(first command-args)))
		  (otherwise 
		    (error "specify at most one branch tag.")))))
    (mcvs-update global-options `(,up-opt ,@command-options))))

(defconstant *branch-help*
"Syntax:

  mcvs branch branch-name

Options:

  none

Semantics

  A branch can sprout from any point in the repository history. The branch
  command makes a branch starting at the closest repository revisions of all
  files in the sandbox, and associates that branch with the given branch name,
  which must be unique among branch names and tags.

  A branch is a fork in the revision history of a project. When a project is
  created, it has one branch which is called the main trunk. Every branch has
  a tip, which consists of the latest committed revisions of the files.
  Committing changes advances the tip to include newer revisions, causing
  the superseded revisions to recede into the branch history. That is how
  the repository grows to include new material, without losing past versions.

  Branches are needed for two reasons: to isolate changes, and to create
  changes based on old work.

  Isolating changes from each other is important for managing the risks
  associated with making changes to software (known as ``change management'').
  Branches decouple the work of making the changes from the decisions about
  what version of the software those changes will be integrated into.  For
  example, branching allows developers to put only critical bugfixes into an
  upcoming software release, while continuing to develop new features for a
  future version after that release. This is done by creating a branch for the
  critical bugfixes, and then eventually making the release from that branch,
  while development takes place on the trunk.  The trunk also needs the
  critical bugfixes that are put into the release.  These fixes don't have to
  be done twice. Rather, the branch is merged to the trunk, which is a mostly
  automatic process, triggered by the invocation of the merge command.
  A branch can also be created to isolate risky experimental changes, so
  that their intergration can be delayed until they are stable, without
  the need to suspend the actual work of writing the changes.

  Secondly, a branch is needed when a change must be made based on file
  revisions that are no longer at the tip of their branch. Since commits happen
  only at the tip, when changes must be based on some historic version rather
  than the latest version, a branch is used. This mechanism allows developers
  to fix a bug in some old version of the software, and send that fix to the
  customer who doesn't want to, or cannot upgrade to the latest version.
  If that fix is pertinent to the latest version of the software, that branch
  can be merged to the trunk; even if the fixed version is very old, it's
  possible that the fix will merge with only a fraction of the effort that
  would be required to re-do the fix.

  Branches are only an important tool; making effective use of branching
  requires that the users understand, agree upon and follow an intelligent
  change management process.")