[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] master 50a4dd3 2/3: Initial commit; copy files from elpa.git
From: |
Stefan Monnier |
Subject: |
[nongnu] master 50a4dd3 2/3: Initial commit; copy files from elpa.git |
Date: |
Sat, 21 Nov 2020 01:14:44 -0500 (EST) |
branch: master
commit 50a4dd3e15552f08a0c86c1f198b9f24d7b13ddf
Author: Stefan Monnier <monnier@iro.umontreal.ca>
Commit: Stefan Monnier <monnier@iro.umontreal.ca>
Initial commit; copy files from elpa.git
---
.gitignore | 15 +
GNUmakefile | 238 ++++++++++++
admin/archive-contents.el | 905 ++++++++++++++++++++++++++++++++++++++++++++++
admin/ert-support.el | 55 +++
admin/forward-diffs.py | 438 ++++++++++++++++++++++
admin/hv.sh | 47 +++
admin/update-archive.sh | 235 ++++++++++++
7 files changed, 1933 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6e08e36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.elc
+*.orig
+.dir-locals?.el
+*~
+\#*\#
+ChangeLog
+core
+/emacs
+/packages/
+/archive/
+
+# Testing file
+/archive
+*.log
+*.buildlog
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..816bb19
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,238 @@
+# Makefile for GNU Emacs Lisp Package Archive.
+#
+
+EMACS=emacs --batch
+
+ARCHIVE_TMP=archive-tmp
+SITE_DIR=site
+
+.PHONY: archive-tmp changelogs process-archive archive-full org-fetch clean
all do-it
+
+all: all-in-place .gitignore
+
+CR_EXCEPTIONS=copyright_exceptions
+.PHONY: check_copyrights
+check_copyrights:
+ @echo "Compute exceptions >$(CR_EXCEPTIONS)~"
+ @export LC_ALL=C; \
+ (cd packages && \
+ find . -name '.git' -prune -o \
+ -name 'test' -prune -o \
+ -name '*.el' -print0 | \
+ xargs -0 grep -L 'Free Software Foundation, Inc' | \
+ grep -v '\(\.dir-locals\|.-\(pkg\|autoloads\)\)\.el$$'; \
+ find . -name '.git' -prune -o -name '*.el' -type f -print | \
+ while read f; do \
+ fquoted="$$(echo $$f|tr '|' '_')"; \
+ sed -n -e '/[Cc]opyright.*, *[1-9][-0-9]*,\?$$/N' \
+ -e '/Free Software Foundation/d' \
+ -e "s|^\\(.*;.*[Cc]opyright\\)|$$fquoted:\\1|p" \
+ "$$f"; \
+ done) | sort >$(CR_EXCEPTIONS)~
+ diff -u "$(CR_EXCEPTIONS)" "$(CR_EXCEPTIONS)~"
+
+## Deploy the package archive to archive/, with packages in
+## archive/packages/:
+archive: archive-tmp
+ $(MAKE) $(MFLAGS) process-archive
+
+archive-tmp: packages
+ -rm -r $(ARCHIVE_TMP)
+ mkdir -p $(ARCHIVE_TMP)
+ cp -a packages/. $(ARCHIVE_TMP)/packages
+
+# Use && after the cd commands, not ;, to ensure the build fails
+# immediately if the directory $(ARCHIVE_TMP)/packages does not exist.
+# For process-archive this is crucial; otherwise batch-make-archive in
+# archive-contents.el will interpret directories in the current
+# directory as unreleased packages, and recursively delete them,
+# including .git. Prior to using &&, running "make process-archive"
+# could silently delete all local git history!
+process-archive:
+ # FIXME, we could probably speed this up significantly with
+ # rules like "%.tar: ../%/ChangeLog" so we only rebuild the packages
+ # that have indeed changed.
+ cd $(ARCHIVE_TMP)/packages && \
+ $(EMACS) -l $(CURDIR)/admin/archive-contents.el \
+ -f batch-make-archive
+ @cd $(ARCHIVE_TMP)/packages && \
+ for pt in *; do \
+ if [ -f "$${pt}/.elpaignore" ]; then \
+ ignore="$${pt}/.elpaignore"; \
+ else \
+ ignore="/dev/null"; \
+ fi; \
+ if [ -d $$pt ]; then \
+ echo "Creating tarball $${pt}.tar" && \
+ tar --exclude-vcs -X "$$ignore" -chf $${pt}.tar $$pt; \
+ rm -rf $${pt}; \
+ fi; \
+ done
+ mkdir -p archive/packages
+ mv archive/packages archive/packages-old
+ mv $(ARCHIVE_TMP)/packages archive/packages
+ chmod -R a+rX archive/packages
+ rm -rf archive/packages-old
+ rm -rf $(ARCHIVE_TMP)
+
+## Deploy the package archive to archive/ including the Org daily:
+archive-full: archive-tmp org-fetch
+ $(MAKE) $(MFLAGS) process-archive
+ #mkdir -p archive/admin
+ #cp admin/* archive/admin/
+
+.gitignore: externals-list
+ $(EMACS) -l $(CURDIR)/admin/archive-contents.el \
+ --eval '(archive-gitignore-externals "$<" "$@")'
+
+# FIXME: Turn it into an `external', which will require adding the notion of
+# "snapshot" packages.
+org-fetch: archive-tmp
+ -cd $(ARCHIVE_TMP)/packages &&
\
+ pkgname=`wget -q -O- https://orgmode.org/elpa/|perl -ne 'push @f, $$1
if m/(org-\d{8})\.tar/; END { @f = sort @f; print "$$f[-1]\n"}'`; \
+ wget -q https://orgmode.org/elpa/$${pkgname}.tar -O $${pkgname}.tar;
\
+ if [ -f $${pkgname}.tar ]; then
\
+ tar xf $${pkgname}.tar;
\
+ rm -f $${pkgname}.tar;
\
+ mv $${pkgname} org;
\
+ fi
+
+clean:
+ rm -rf archive $(ARCHIVE_TMP) $(SITE_DIR)
+
+########## Rules for in-place installation ####################################
+pkgs := $(foreach pkg, $(wildcard packages/*), \
+ $(if $(shell [ -d "$(pkg)" ] && echo true), $(pkg)))
+
+define SET-diff
+$(shell $(file > .tmp.setdiff, $(1)) \
+ $(file >> .tmp.setdiff, $(2)) \
+ $(file >> .tmp.setdiff, $(2)) \
+ tr ' ' '\n' < .tmp.setdiff | sort | uniq -u ; rm .tmp.setdiff)
+endef
+
+define FILTER-nonsrc
+$(filter-out %-autoloads.el %-pkg.el %/.dir-locals.el, $(1))
+endef
+
+define RULE-srcdeps
+$(1): $$(call FILTER-nonsrc, $$(wildcard $$(dir $(1))/*.el))
+endef
+
+# Compute the set of autolods files and their dependencies.
+autoloads := $(foreach pkg, $(pkgs), $(pkg)/$(notdir $(pkg))-autoloads.el)
+
+# FIXME: In 99% of the cases, autoloads can be generated in any order.
+# But the `names' package is an exception because it sets up an advice that
+# changes the way autload.el operates, and that advice is needed when creating
+# the autoloads file of packages that use `names'.
+# The right solution is to check the Package-Requires and create the autoloads
+# files in topological order, but for now we can just do it the ad-hoc way and
+# add hand-made dependencies between autoloads files, and explicitly
+# load the names-autoloads file when building autoloads files. An example entry
+# is commented below, this is what should be done if a package depends on
Names.
+
+# packages/aggressive-indent/aggressive-indent-autoloads.el: \
+# packages/names/names-autoloads.el
+
+$(foreach al, $(autoloads), $(eval $(call RULE-srcdeps, $(al))))
+%-autoloads.el:
+ @#echo 'Generating autoloads for $@'
+ @cd $(dir $@) && \
+ $(EMACS) -l $(CURDIR)/admin/archive-contents.el \
+ --eval "(require 'package)" \
+ --eval "(load (expand-file-name \"../names/names-autoloads.el\")
t t)" \
+ --eval "(package-generate-autoloads \"$$(basename $$(pwd))\" \
+ \"$$(pwd)\")"
+
+# Put into elcs the set of elc files we need to keep up-to-date.
+# I.e. one for each .el file in each package root, except for the -pkg.el,
+# the -autoloads.el, the .el files that are marked "no-byte-compile", and
+# files matching patterns in packages' .elpaignore files.
+included_els := $(shell tar -cvhf /dev/null --exclude-ignore=.elpaignore \
+ --exclude-vcs packages 2>&1 | grep '\.el$$')
+
+# included_els := $(wildcard packages/*/*.el)
+
+# els := $(call FILTER-nonsrc, $(wildcard packages/*/*.el \
+# packages/*/*/*.el \
+# packages/*/*/*/*.el \
+# packages/*/*/*/*/*.el))
+els := $(call FILTER-nonsrc, $(included_els))
+naive_elcs := $(patsubst %.el, %.elc, $(els))
+current_elcs := $(shell find packages -name '*.elc' -print)
+
+extra_els := $(call SET-diff, $(els), $(patsubst %.elc, %.el, $(current_elcs)))
+nbc_els := $(foreach el, $(extra_els), \
+ $(if $(shell grep '^;.*no-byte-compile: *t' "$(el)"), $(el)))
+elcs := $(call SET-diff, $(naive_elcs), $(patsubst %.el, %.elc, $(nbc_els)))
+
+# '(dolist (al (quote ($(patsubst %, "%", $(autoloads))))) (load
(expand-file-name al) nil t))'
+%.elc: %.el
+ @echo 'Byte compiling $<'
+ @$(EMACS) \
+ --eval "(setq package-directory-list nil \
+ load-prefer-newer t \
+ package-user-dir \"$(abspath packages)\")" \
+ -f package-initialize \
+ -L $(dir $@) -f batch-byte-compile $<
+
+.PHONY: elcs
+elcs: $(elcs)
+
+# Remove .elc files that don't have a corresponding .el file any more.
+extra_elcs := $(call SET-diff, $(current_elcs), $(naive_elcs))
+.PHONY: $(extra_elcs)
+$(extra_elcs):; rm $@
+
+# # Put into single_pkgs the set of -pkg.el files we need to keep up-to-date.
+# # I.e. all the -pkg.el files for the single-file packages.
+pkg_descs:=$(foreach pkg, $(pkgs), $(pkg)/$(notdir $(pkg))-pkg.el)
+#$(foreach al, $(single_pkgs), $(eval $(call RULE-srcdeps, $(al))))
+%-pkg.el: %.el
+ @echo 'Generating description file $@'
+ @$(EMACS) \
+ --eval '(require (quote package))' \
+ --eval '(setq b (find-file-noselect "$<"))' \
+ --eval '(setq d (with-current-buffer b (package-buffer-info)))' \
+ --eval '(package-generate-description-file d "$@")'
+
+.PHONY: all-in-place
+# Use order-only prerequisites, so that autoloads are done first.
+all-in-place: | $(extra_elcs) $(autoloads) $(pkg_descs) elcs
+
+
+############### Rules to prepare the externals ################################
+
+.PHONY:
+externals:
+ $(EMACS) -l admin/archive-contents.el \
+ -f archive-add/remove/update-externals
+
+
+
+
+################### Testing ###############
+
+PACKAGE_DIRS = $(shell find packages -maxdepth 1 -type d)
+PACKAGES=$(subst /,,$(subst packages,,$(PACKAGE_DIRS)))
+
+TOP =$(shell pwd)
+
+define test_template
+$(1)-test:
+ cd packages/$(1);\
+ $(EMACS) -l $(TOP)/admin/ert-support.el \
+ --eval "(ert-support-test-package \"$(TOP)\" '$(1))" \
+
+$(1)-test-log:
+ $(MAKE) $(1)-test > packages/$(1)/$(1).log 2>&1 || { stat=ERROR; }
+endef
+
+$(foreach package,$(PACKAGES),$(eval $(call test_template,$(package))))
+
+PACKAGES_TESTS=$(addsuffix -test-log,$(PACKAGES))
+PACKAGES_LOG=$(foreach package,$(PACKAGES),packages/$(package)/$(package).log)
+
+check: $(PACKAGES_TESTS)
+ $(EMACS) -l ert -f ert-summarize-tests-batch-and-exit $(PACKAGES_LOG)
diff --git a/admin/archive-contents.el b/admin/archive-contents.el
new file mode 100644
index 0000000..0ee3fc2
--- /dev/null
+++ b/admin/archive-contents.el
@@ -0,0 +1,905 @@
+;;; archive-contents.el --- Auto-generate an Emacs Lisp package archive. -*-
lexical-binding:t -*-
+
+;; Copyright (C) 2011-2019 Free Software Foundation, Inc
+
+;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(eval-when-compile (require 'cl-lib))
+(require 'lisp-mnt)
+(require 'package)
+(require 'pcase)
+
+(defconst archive-contents-subdirectory-regexp
+
"\\([^.].*?\\)-\\([0-9]+\\(?:[.][0-9]+\\|\\(?:pre\\|beta\\|alpha\\)[0-9]+\\)*\\)")
+
+(defconst archive-re-no-dot "\\`\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"
+ "Regular expression matching all files except \".\" and \"..\".")
+
+(defun archive--version-to-list (vers)
+ (when vers
+ (let ((l (version-to-list vers)))
+ ;; Signal an error for things like "1.02" which is parsed as "1.2".
+ (cl-assert (equal vers (package-version-join l)) nil
+ "Unsupported version syntax %S" vers)
+ l)))
+
+(defun archive--convert-require (elt)
+ (let ((vers (archive--version-to-list (car (cdr elt)))))
+ (if vers
+ (list (car elt) vers)
+ (list (car elt)))))
+
+(defun archive--dirname (dir &optional base)
+ (file-name-as-directory (expand-file-name dir base)))
+
+(defun archive--delete-elc-files (dir &optional only-orphans)
+ "Recursively delete all .elc files in DIR.
+Delete backup files also."
+ (dolist (f (directory-files dir t archive-re-no-dot))
+ (cond ((file-directory-p f)
+ (archive--delete-elc-files f))
+ ((or (and (string-match "\\.elc\\'" f)
+ (not (and only-orphans
+ (file-readable-p (replace-match ".el" t t f)))))
+ (backup-file-name-p f))
+ (delete-file f)))))
+
+(defun batch-make-archive ()
+ "Process package content directories and generate the archive-contents file."
+ (let ((packages '(1))) ; format-version.
+ (dolist (dir (directory-files default-directory nil archive-re-no-dot))
+ (condition-case v
+ (if (not (file-directory-p dir))
+ (message "Skipping non-package file %s" dir)
+ (let* ((pkg (file-name-nondirectory dir))
+ (autoloads-file (expand-file-name (concat pkg
"-autoloads.el") dir)))
+ ;; Omit autoloads and .elc files from the package.
+ (when (file-exists-p autoloads-file)
+ (delete-file autoloads-file))
+ (archive--delete-elc-files dir)
+ (let ((metadata (or (with-demoted-errors
+ ;;(format "batch-make-archive %s: %%s" dir)
+ (archive--metadata dir pkg))
+ '(nil "0"))))
+ ;; (nth 1 metadata) is nil for "org" which is the only package
+ ;; still using the "org-pkg.el file to specify the metadata.
+ (if (and (nth 1 metadata)
+ (or (equal (nth 1 metadata) "0")
+ ;; Old deprecated convention.
+ (< (string-to-number (nth 1 metadata)) 0)))
+ (progn ;; Negative version: don't publish this package yet!
+ (message "Package %s not released yet!" dir)
+ (delete-directory dir 'recursive))
+ (push (if (car metadata)
+ (apply #'archive--process-simple-package
+ dir pkg (cdr metadata))
+ (when (nth 1 metadata)
+ (archive--write-pkg-file dir pkg metadata))
+ (archive--process-multi-file-package dir pkg))
+ packages)))))
+ ((debug error) (error "Error in %s: %S" dir v))))
+ (with-temp-buffer
+ (pp (nreverse packages) (current-buffer))
+ (write-region nil nil "archive-contents"))))
+
+(defun archive-call (destination program &rest args)
+ "Like ‘call-process’ for PROGRAM, DESTINATION, ARGS.
+The INFILE and DISPLAY arguments are fixed as nil."
+ (apply #'call-process program nil destination nil args))
+
+(defconst archive--revno-re "[0-9a-f]+")
+
+(defun archive-prepare-packages (srcdir)
+ "Prepare the `packages' directory inside the Git checkout.
+Expects to be called from within the `packages' directory.
+\"Prepare\" here is for subsequent construction of the packages and archive,
+so it is meant to refresh any generated files we may need.
+Currently only refreshes the ChangeLog files."
+ (setq srcdir (archive--dirname srcdir))
+ (let* ((wit ".changelog-witness")
+ (prevno (with-temp-buffer
+ (insert-file-contents wit)
+ (if (looking-at (concat archive--revno-re "$"))
+ (match-string 0)
+ (error "Can't find previous revision name"))))
+ (new-revno
+ (or (with-temp-buffer
+ (let ((default-directory srcdir))
+ (archive-call '(t) "git" "rev-parse" "HEAD")
+ (goto-char (point-min))
+ (when (looking-at (concat archive--revno-re "$"))
+ (match-string 0))))
+ (error "Couldn't find the current revision's name")))
+ (pkgs '()))
+ (unless (equal prevno new-revno)
+ (with-temp-buffer
+ (let ((default-directory srcdir))
+ (unless (zerop (archive-call '(t) "git" "diff"
+ "--dirstat=cumulative,0"
+ prevno))
+ (error "Error signaled by git diff --dirstat %d" prevno)))
+ (goto-char (point-min))
+ (while (re-search-forward "^[ \t.0-9%]* packages/\\([-[:alnum:]]+\\)/$"
+ nil t)
+ (push (match-string 1) pkgs))))
+ (let ((default-directory (expand-file-name "packages/")))
+ (dolist (pkg pkgs)
+ (condition-case v
+ (when (file-directory-p pkg)
+ (archive--make-changelog pkg (expand-file-name "packages/"
+ srcdir)))
+ (error (message
+ "Error in archive-prepare-packages for package %S:\n %S"
+ pkg v)))))
+ (write-region new-revno nil wit nil 'quiet)
+ ;; Also update the ChangeLog of external packages.
+ (let ((default-directory (expand-file-name "packages/")))
+ (dolist (dir (directory-files "."))
+ (and (not (member dir '("." "..")))
+ (file-directory-p dir)
+ (let* ((gitdir (expand-file-name
+ (concat "packages/" dir "/.git")
+ srcdir))
+ (index (cond
+ ((file-directory-p gitdir)
+ (expand-file-name
+ (concat "packages/" dir "/.git/index")
+ srcdir))
+ ((file-readable-p gitdir)
+ (with-temp-buffer
+ (insert-file-contents gitdir)
+ (goto-char (point-min))
+ (if (looking-at "gitdir:[ \t]*")
+ (progn
+ (delete-region (match-beginning 0)
+ (match-end 0))
+ (expand-file-name "index"
(buffer-string)))
+ (message "Can't find gitdir in %S" gitdir)
+ nil)))
+ (t nil)))
+ (cl (expand-file-name "ChangeLog" dir)))
+ (and index
+ (file-exists-p index)
+ (or (not (file-exists-p cl))
+ (file-newer-than-file-p index cl))))
+ (archive--make-changelog
+ dir (expand-file-name "packages/" srcdir)))))
+ ))
+
+(defconst archive-default-url-format "http://elpa.gnu.org/packages/%s.html")
+(defconst archive-default-url-re (format archive-default-url-format ".*"))
+
+(defun archive--metadata (dir pkg)
+ "Return a list (SIMPLE VERSION DESCRIPTION REQ EXTRAS),
+where SIMPLE is non-nil if the package is simple;
+VERSION is the version string of the simple package;
+DESCRIPTION is the brief description of the package;
+REQ is a list of requirements;
+EXTRAS is an alist with additional metadata.
+
+PKG is the name of the package and DIR is the directory where it is."
+ (let* ((mainfile (expand-file-name (concat pkg ".el") dir))
+ (files (directory-files dir nil "\\`dir\\'\\|\\.el\\'")))
+ (setq files (delete (concat pkg "-pkg.el") files))
+ (setq files (delete (concat pkg "-autoloads.el") files))
+ (cond
+ ((file-exists-p mainfile)
+ (with-temp-buffer
+ (insert-file-contents mainfile)
+ (goto-char (point-min))
+ (let* ((pkg-desc (package-buffer-info))
+ (extras (package-desc-extras pkg-desc))
+ (version (package-desc-version pkg-desc))
+ (keywords (lm-keywords-list))
+ ;; (_ (archive--version-to-list version)) ; Sanity check!
+ (pt (lm-header "package-type"))
+ (simple (if pt (equal pt "simple") (= (length files) 1)))
+ (found-url (alist-get :url extras))
+ (found-keywords (alist-get :keywords extras)))
+
+ (when (and keywords (not found-keywords))
+ ;; Using an old package-buffer-info which doesn't include
+ ;; keywords. Fix it by hand.
+ (push (cons :keywords keywords) extras))
+ (unless found-url
+ ;; Provide a good default URL.
+ (push (cons :url (format archive-default-url-format pkg)) extras))
+ (list simple
+ (package-version-join version)
+ (package-desc-summary pkg-desc)
+ (package-desc-reqs pkg-desc)
+ extras))))
+ (t
+ (error "Can't find main file %s file in %s" mainfile dir)))))
+
+(defun archive--process-simple-package (dir pkg vers desc req extras)
+ "Deploy the contents of DIR into the archive as a simple package.
+Rename DIR/PKG.el to PKG-VERS.el, delete DIR, and return the descriptor."
+ ;; Write DIR/foo.el to foo-VERS.el and delete DIR
+ (let ((src (expand-file-name (concat pkg ".el") dir)))
+ (funcall (if (file-symlink-p src) #'copy-file #'rename-file)
+ src (concat pkg "-" vers ".el")))
+ ;; Add the content of the ChangeLog.
+ (let ((cl (expand-file-name "ChangeLog" dir)))
+ (with-current-buffer (find-file-noselect (concat pkg "-" vers ".el"))
+ (goto-char (point-max))
+ (re-search-backward "^;;;.*ends here")
+ (re-search-backward "^(provide")
+ (skip-chars-backward " \t\n")
+ (insert "\n\n;;;; ChangeLog:\n\n")
+ (let* ((start (point))
+ (end (copy-marker start t)))
+ (condition-case nil
+ (insert-file-contents cl)
+ (file-error (message "Can't find %S's ChangeLog file" pkg)))
+ (goto-char end)
+ (unless (bolp) (insert "\n"))
+ (while (progn (forward-line -1) (>= (point) start))
+ (insert ";; ")))
+ (set (make-local-variable 'backup-inhibited) t)
+ (basic-save-buffer) ;Less chatty than save-buffer.
+ (kill-buffer)))
+ (delete-directory dir t)
+ (cons (intern pkg) (vector (archive--version-to-list vers)
+ req desc 'single extras)))
+
+(defun archive--make-changelog (dir srcdir)
+ "Export Git log info of DIR into a ChangeLog file."
+ (message "Refreshing ChangeLog in %S" dir)
+ (let ((default-directory (archive--dirname dir)))
+ (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (let ((coding-system-for-read 'binary)
+ (coding-system-for-write 'binary))
+ (when (file-readable-p "ChangeLog") (insert-file-contents "ChangeLog"))
+ (let ((old-md5 (md5 (current-buffer))))
+ (erase-buffer)
+ (let ((default-directory (archive--dirname dir srcdir)))
+ (archive-call (current-buffer) ; hmm, why not use ‘t’ here? --ttn
+ "git" "log" "--date=short"
+ "--format=%cd %aN <%ae>%n%n%w(80,8,8)%B%n"
+ "."))
+ (tabify (point-min) (point-max))
+ (goto-char (point-min))
+ (while (re-search-forward "\n\n\n+" nil t)
+ (replace-match "\n\n"))
+ (if (equal old-md5 (md5 (current-buffer)))
+ (message "ChangeLog's md5 unchanged for %S" dir)
+ (write-region (point-min) (point-max) "ChangeLog" nil 'quiet)))))))
+
+(defun archive--alist-to-plist-args (alist)
+ (mapcar (lambda (x)
+ (if (and (not (consp x))
+ (or (keywordp x)
+ (not (symbolp x))
+ (memq x '(nil t))))
+ x `',x))
+ (apply #'nconc
+ (mapcar (lambda (pair) (list (car pair) (cdr pair))) alist))))
+
+(defun archive--plist-args-to-alist (plist)
+ (let (alist)
+ (while plist
+ (let ((value (cadr plist)))
+ (when value
+ (cl-assert (keywordp (car plist)))
+ (push (cons (car plist)
+ (if (eq 'quote (car-safe value)) (cadr value) value))
+ alist)))
+ (setq plist (cddr plist)))
+ alist))
+
+(defun archive--process-multi-file-package (dir pkg)
+ "Deploy the contents of DIR into the archive as a multi-file package.
+Rename DIR/ to PKG-VERS/, and return the descriptor."
+ (let* ((exp (archive--multi-file-package-def dir pkg))
+ (vers (nth 2 exp))
+ (req-exp (nth 4 exp))
+ (req (mapcar #'archive--convert-require
+ (if (eq 'quote (car-safe req-exp)) (nth 1 req-exp)
+ (when req-exp
+ (error "REQ should be a quoted constant: %S"
+ req-exp)))))
+ (extras (archive--plist-args-to-alist (nthcdr 5 exp))))
+ (unless (equal (nth 1 exp) pkg)
+ (error (format "Package name %s doesn't match file name %s"
+ (nth 1 exp) pkg)))
+ (rename-file dir (concat pkg "-" vers))
+ (cons (intern pkg) (vector (archive--version-to-list vers)
+ req (nth 3 exp) 'tar extras))))
+
+(defun archive--form-from-file-contents (filename)
+ (with-temp-buffer
+ (insert-file-contents filename)
+ ;; This is unnecessary because ‘with-temp-buffer’ generates a new
+ ;; (empty) buffer, and ‘insert-file-contents’ inserts after point.
+ ;; In other words, point is alraedy at bob.
+ ;;- (goto-char (point-min))
+ (read (current-buffer))))
+
+(defun archive--multi-file-package-def (dir pkg)
+ "Return the `define-package' form in the file DIR/PKG-pkg.el."
+ (let ((pkg-file (expand-file-name (concat pkg "-pkg.el") dir)))
+ (unless (file-exists-p pkg-file)
+ (error "File not found: %s" pkg-file))
+ (archive--form-from-file-contents pkg-file)))
+
+(defun archive-refresh-pkg-file ()
+ ;; Note: Used via --batch by GNUmakefile rule.
+ (let* ((dir (directory-file-name default-directory))
+ (pkg (file-name-nondirectory dir)))
+ (archive--write-pkg-file dir pkg (archive--metadata dir pkg))))
+
+(defun archive--write-pkg-file (pkg-dir name metadata)
+ ;; FIXME: Use package-generate-description-file!
+ (let ((pkg-file (expand-file-name (concat name "-pkg.el") pkg-dir))
+ (print-level nil)
+ (print-quoted t)
+ (print-length nil))
+ (write-region
+ (concat (format ";; Generated package description from %s.el -*-
no-byte-compile: t -*-\n"
+ name)
+ (prin1-to-string
+ (cl-destructuring-bind (version desc requires extras)
+ (cdr metadata)
+ (nconc
+ (list 'define-package
+ name
+ version
+ desc
+ (list 'quote
+ ;; Turn version lists into string form.
+ (mapcar
+ (lambda (elt)
+ (list (car elt)
+ (package-version-join (cadr elt))))
+ requires)))
+ (archive--alist-to-plist-args extras))))
+ "\n")
+ nil
+ pkg-file)))
+
+;;; Make the HTML pages for online browsing.
+
+(defun archive--html-header (title &optional header)
+ (format "<!DOCTYPE HTML PUBLIC>
+<html lang=\"en\" xml:lang=\"en\">
+ <head>
+ <title>%s</title>
+ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
+ <link rel=\"shortcut icon\" type=\"image/png\" href=\"../favicon.png\">
+ <link rel=\"stylesheet\"
href=\"//code.cdn.mozilla.net/fonts/fira.css\">
+ <link rel=\"stylesheet\" type=\"text/css\" href=\"../layout.css\">
+ <script src=\"../javascript/jquery.min.js\"
type=\"text/javascript\"></script>
+ <script src=\"../javascript/jquery.filtertable.min.js\"
type=\"text/javascript\"></script>
+ <script src=\"../javascript/package-search.js\"
type=\"text/javascript\"></script>
+ <meta name=\"viewport\"
content=\"initial-scale=1.0,maximum-scale=1.0,width=device-width\" />
+ </head>
+ <body>
+
+ <div class=\"wrapper\">
+
+ <div class=\"header small\">
+ <div class=\"container\">
+ <h1>%s</h1>
+ </div>
+ </div>
+
+ <div class=\"container\">\n"
+ title (or header title)))
+
+(defun archive--html-bytes-format (bytes) ;Aka memory-usage-format.
+ (setq bytes (/ bytes 1024.0))
+ (let ((units '("KiB" "MiB" "GiB" "TiB")))
+ (while (>= bytes 1024)
+ (setq bytes (/ bytes 1024.0))
+ (setq units (cdr units)))
+ (cond
+ ((>= bytes 100) (format "%4.0f %s" bytes (car units)))
+ ((>= bytes 10) (format "%4.1f %s" bytes (car units)))
+ (t (format "%4.2f %s" bytes (car units))))))
+
+(defun archive--get-prop (prop name srcdir mainsrcfile)
+ (let ((kprop (intern (format ":%s" (downcase prop)))))
+ (or
+ (let ((pkgdescfile (expand-file-name (format "%s-pkg.el" name)
+ srcdir)))
+ (when (file-readable-p pkgdescfile)
+ (let ((desc (archive--form-from-file-contents pkgdescfile)))
+ (plist-get (cdr desc) kprop))))
+ (when (file-readable-p mainsrcfile)
+ (with-temp-buffer
+ (insert-file-contents mainsrcfile)
+ (lm-header prop))))))
+
+(defun archive--get-section (hsection fsection srcdir mainsrcfile)
+ (when (consp fsection)
+ (while (cdr-safe fsection)
+ (setq fsection
+ (if (file-readable-p (expand-file-name (car fsection) srcdir))
+ (car fsection)
+ (cdr fsection))))
+ (when (consp fsection) (setq fsection (car fsection))))
+ (cond
+ ((file-readable-p (expand-file-name fsection srcdir))
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name fsection srcdir))
+ (buffer-string)))
+ ((file-readable-p mainsrcfile)
+ (with-temp-buffer
+ (insert-file-contents mainsrcfile)
+ (emacs-lisp-mode) ;lm-section-start needs the outline-mode setting.
+ (let ((start (lm-section-start hsection)))
+ (when start
+ (insert
+ (prog1
+ (buffer-substring start (lm-section-end hsection))
+ (erase-buffer)))
+ (emacs-lisp-mode)
+ (goto-char (point-min))
+ (delete-region (point) (line-beginning-position 2))
+ (uncomment-region (point-min) (point-max))
+ (when (looking-at "^\\([ \t]*\n\\)+")
+ (replace-match ""))
+ (goto-char (point-max))
+ (skip-chars-backward " \t\n")
+ (delete-region (point) (point-max))
+ (buffer-string)))))))
+
+(defun archive--quote (txt)
+ (replace-regexp-in-string "<" "<"
+ (replace-regexp-in-string "&" "&" txt)))
+
+(defun archive--read-externals-list (&optional dir)
+ (archive--form-from-file-contents
+ (expand-file-name "externals-list" dir)))
+
+(defun archive--insert-repolinks (name srcdir _mainsrcfile url)
+ (when url
+ (insert (format "<dt>Home page</dt> <dd><a href=%S>%s</a></dd>\n"
+ url (archive--quote url)))
+ (when (string-match archive-default-url-re url)
+ (setq url nil)))
+ (let* ((externals (archive--read-externals-list
+ (expand-file-name "../../../elpa" srcdir)))
+ (extern-desc (assoc name externals))
+ (git-sv "http://git.savannah.gnu.org/")
+ (urls
+ (if (eq (nth 1 extern-desc) :core)
+ (let* ((files (nth 2 extern-desc))
+ (file (if (listp files)
+ (directory-file-name
+ (file-name-directory
+ (try-completion "" files)))
+ files)))
+ (mapcar (lambda (s) (concat s file))
+ `("cgit/emacs.git/tree/"
+ ,(if (listp files)
+ "gitweb/?p=emacs.git;a=tree;f="
+ "gitweb/?p=emacs.git;a=blob;f="))))
+ (mapcar (lambda (s) (concat s name))
+ (if (eq (nth 1 extern-desc) :external)
+ '("cgit/emacs/elpa.git/?h=externals/"
+
"gitweb/?p=emacs/elpa.git;a=shortlog;h=refs/heads/externals/")
+ '("cgit/emacs/elpa.git/tree/packages/"
+ "gitweb/?p=emacs/elpa.git;a=tree;f=packages/"))))))
+ (insert (format
+ (concat (format "<dt>Browse %srepository</dt> <dd>" (if url
"ELPA's " ""))
+ "<a href=%S>%s</a> or <a href=%S>%s</a></dd>\n")
+ (concat git-sv (nth 0 urls))
+ 'CGit
+ (concat git-sv (nth 1 urls))
+ 'Gitweb))))
+
+(defun archive--html-make-pkg (pkg files)
+ (let* ((name (symbol-name (car pkg)))
+ (latest (package-version-join (aref (cdr pkg) 0)))
+ (srcdir (expand-file-name name "../../build/packages"))
+ (mainsrcfile (expand-file-name (format "%s.el" name) srcdir))
+ (desc (aref (cdr pkg) 2)))
+ (with-temp-buffer
+ (insert (archive--html-header
+ (format "GNU ELPA - %s" name)
+ (format "<a href=\"index.html\">GNU ELPA</a> - %s" name)))
+ (insert (format "<h2 class=\"package\">%s</h2>" name))
+ (insert "<dl>")
+ (insert (format "<dt>Description</dt><dd>%s</dd>\n" (archive--quote
desc)))
+ (if (zerop (length latest))
+ (insert "<dd>This package "
+ (if files "is not in GNU ELPA any more"
+ "has not been released yet")
+ ".</dd>\n")
+ (let* ((file (cdr (assoc latest files)))
+ (attrs (file-attributes file)))
+ (insert (format "<dt>Latest</dt> <dd><a href=%S>%s</a>, %s,
%s</dd>\n"
+ file (archive--quote file)
+ (format-time-string "%Y-%b-%d" (nth 5 attrs))
+ (archive--html-bytes-format (nth 7 attrs))))))
+ (let ((maint (archive--get-prop "Maintainer" name srcdir mainsrcfile)))
+ (when maint
+ (insert (format "<dt>Maintainer</dt> <dd>%s</dd>\n" (archive--quote
maint)))))
+ (archive--insert-repolinks
+ name srcdir mainsrcfile
+ (or (cdr (assoc :url (aref (cdr pkg) 4)))
+ (archive--get-prop "URL" name srcdir mainsrcfile)))
+ (insert "</dl>")
+ (insert (format "<p>To install this package, run in Emacs:</p>
+ <pre>M-x <span class=\"kw\">package-install</span> RET
<span class=\"kw\">%s</span> RET</pre>"
+ name))
+ (let ((rm (archive--get-section
+ "Commentary" '("README" "README.rst"
+ ;; Most README.md files seem to be currently
+ ;; worse than the Commentary: section :-(
+ ;; "README.md"
+ "README.org")
+ srcdir mainsrcfile)))
+ (when rm
+ (write-region rm nil (concat name "-readme.txt"))
+ (insert "<h2>Full description</h2><pre>\n" (archive--quote rm)
+ "\n</pre>\n")))
+ (unless (< (length files) (if (zerop (length latest)) 1 2))
+ (insert (format "<h2>Old versions</h2><table>\n"))
+ (dolist (file
+ (sort files (lambda (f1 f2) (version< (car f2) (car f1)))))
+ (unless (equal (pop file) latest)
+ (let ((attrs (file-attributes file)))
+ (insert (format "<tr><td><a
href=%S>%s</a></td><td>%s</td><td>%s</td>\n"
+ file (archive--quote file)
+ (format-time-string "%Y-%b-%d" (nth 5 attrs))
+ (archive--html-bytes-format (nth 7 attrs)))))))
+ (insert "</table>\n"))
+ (let ((news (archive--get-section
+ "News" '("NEWS" "NEWS.rst" "NEWS.md" "NEWS.org")
+ srcdir mainsrcfile)))
+ (when news
+ (insert "<h2>News</h2><pre>\n" (archive--quote news) "\n</pre>\n")))
+ (insert "</body>\n")
+ (write-region (point-min) (point-max) (concat name ".html")))))
+
+(defun archive--html-make-index (pkgs)
+ (with-temp-buffer
+ (insert (archive--html-header "GNU ELPA Packages"))
+ (insert "<table>\n")
+ (insert "<tr><th>Package</th><th>Version</th><th>Description</th></tr>\n")
+ (dolist (pkg pkgs)
+ (insert (format "<tr><td><a
href=\"%s.html\">%s</a></td><td>%s</td><td>%s</td></tr>\n"
+ (car pkg) (car pkg)
+ (package-version-join (aref (cdr pkg) 0))
+ (aref (cdr pkg) 2))))
+ (insert " </table>
+ </div>
+ <div class=\"push\"></div>
+ </div>
+
+ <div class=\"footer\">
+ <div class=\"container\">
+ <p>Copyright 2016 <a href=\"https://fsf.org\">Free Software
Foundation</a>, Inc.</p>
+ <p>Design provided by <a
href=\"http://nicolas.petton.fr\">Nicolas Petton</a></p>
+ <p>
+ This website is licensed under the
+ <a
href=\"https://creativecommons.org/licenses/by-nd/3.0/us/\">CC BY-ND 3.0</a>
+ US License.
+ </p>
+ </div>
+ </div>
+
+</body>\n")
+ (write-region (point-min) (point-max) "index.html")))
+
+(defun batch-html-make-index ()
+ (let ((packages (make-hash-table :test #'equal))
+ (archive-contents
+ ;; Skip the first element which is a version number.
+ (cdr (archive--form-from-file-contents "archive-contents"))))
+ (dolist (subdir (directory-files "../../build/packages" nil))
+ (cond
+ ((member subdir '("." ".." "elpa.rss" "index.html" "archive-contents")))
+ (t (puthash subdir nil packages))))
+ (dolist (file (directory-files default-directory nil))
+ (cond
+ ((member file '("." ".." "elpa.rss" "index.html" "archive-contents")))
+ ((string-match "\\.html\\'" file))
+ ((string-match "\\.sig\\'" file))
+ ((string-match "-readme\\.txt\\'" file)
+ (let ((name (substring file 0 (match-beginning 0))))
+ (puthash name (gethash name packages) packages)))
+ ((string-match "-\\([0-9][^-]*\\)\\.\\(tar\\|el\\)\\'" file)
+ (let ((name (substring file 0 (match-beginning 0)))
+ (version (match-string 1 file)))
+ (push (cons version file) (gethash name packages))))
+ (t (message "Unknown file %S" file))))
+ (maphash (lambda (pkg-name files)
+ (archive--html-make-pkg
+ (let ((pkg (intern pkg-name)))
+ (or (assq pkg archive-contents)
+ ;; Add entries for packages that are either not yet
+ ;; released or not released any more.
+ ;; FIXME: Get actual description!
+ (let ((entry (cons pkg (vector nil nil "" nil nil))))
+ (setq archive-contents
+ ;; Add entry at the end.
+ (nconc archive-contents (list entry)))
+ entry)))
+ files))
+ packages)
+ (archive--html-make-index archive-contents)))
+
+(defun archive--pull (dirname)
+ (let ((default-directory (archive--dirname dirname)))
+ (with-temp-buffer
+ (cond
+ ((file-directory-p ".git")
+ (message "Running git pull in %S" default-directory)
+ (archive-call t "git" "pull"))
+ ((file-exists-p ".git")
+ (unless (with-temp-buffer
+ (archive-call t "git" "status" "--branch" "--porcelain=2")
+ (goto-char (point-min))
+ ;; Nothing to pull (nor push, actually).
+ (search-forward "\n# branch.ab +0 -0" nil t))
+ (message "Updating worktree in %S" default-directory)
+ (archive-call t "git" "merge")))
+ (t (error "No .git in %S" default-directory)))
+ (unless (and (eobp) (bobp))
+ (message "Updated %s:%s%s" dirname
+ (if (and (eobp) (bolp)
+ (eq (line-beginning-position 0) (point-min)))
+ " " "\n")
+ (buffer-string))))))
+
+;;; Maintain external packages.
+
+(defconst archive--elpa-git-url "git://git.sv.gnu.org/emacs/elpa")
+(defconst archive--emacs-git-url "git://git.sv.gnu.org/emacs.git")
+
+(defun archive--sync-emacs-repo ()
+ "Sync Emacs repository, if applicable.
+Return non-nil if there's an \"emacs\" repository present."
+ ;; Support for :core packages is important for elpa.gnu.org, but for other
+ ;; cases such as "in-place installation", it's rather secondary since
+ ;; those users can just as well use a development version of Emacs to get
+ ;; those packages.
+ ;; So make the handling of :core packages depend on whether or not the user
+ ;; has setup a clone of Emacs under the "emacs" subdirectory.
+ (let ((emacs-repo-root (expand-file-name "emacs")))
+ (if (not (file-directory-p emacs-repo-root))
+ (progn (message "No \"emacs\" subdir: will skip :core packages")
+ nil)
+ (archive--pull emacs-repo-root)
+ t)))
+
+(defun archive--find-non-trivial-file (dir)
+ (catch 'found-important-file
+ (dolist (file (directory-files-recursively dir ".*"))
+ (unless (or (member file '("." ".."))
+ (string-match "\\.elc\\'" file)
+ (string-match "-autoloads.el\\'" file)
+ (string-match "-pkg.el\\'" file)
+ (file-symlink-p file))
+ (throw 'found-important-file file)))
+ nil))
+
+(defun archive--cleanup-packages (externals-list with-core)
+ "Remove unknown subdirectories of `packages/'.
+This is any subdirectory inside `packages/' that's not under
+version control nor listed in EXTERNALS-LIST.
+If WITH-CORE is non-nil, it means we manage :core packages as well."
+ (let ((default-directory (expand-file-name "packages/")))
+ (dolist (dir (directory-files "."))
+ (cond
+ ((file-symlink-p dir)
+ ;; There are normally no such thing, but the user may elect to
+ ;; add symlinks to other projects. If so, update them, as if they
+ ;; were "externals".
+ (when (file-directory-p (expand-file-name ".git" dir))
+ (archive--pull dir)))
+ ((or (not (file-directory-p dir)) )
+ ;; We only add/remove plain directories in elpa/packages (not
+ ;; symlinks).
+ nil)
+ ((member dir '("." "..")) nil)
+ ((assoc dir externals-list) nil)
+ ((file-directory-p (expand-file-name (format "%s/.git" dir)))
+ (let ((status
+ (with-temp-buffer
+ (let ((default-directory (archive--dirname dir)))
+ (archive-call t "git" "status" "--porcelain")
+ (buffer-string)))))
+ (if (zerop (length status))
+ (progn (delete-directory dir 'recursive t)
+ (message "Deleted all of %s" dir))
+ (message "Keeping leftover unclean %s:\n%s" dir status))))
+ ;; Check if `dir' is under version control.
+ ((and with-core
+ (not (zerop (archive-call nil "git" "ls-files"
+ "--error-unmatch" dir))))
+ ;; Not under version control. Check if it only contains
+ ;; symlinks and generated files, in which case it is probably
+ ;; a leftover :core package that can safely be deleted.
+ ;; (let ((file (archive--find-non-trivial-file dir)))
+ ;; (if file
+ ;; (message "Keeping %s for non-trivial file \"%s\"" dir file)
+ ;; (progn
+ ;; (message "Deleted untracked package %s" dir)
+ ;; (delete-directory dir 'recursive t))))
+ )))))
+
+(defvar archive--use-worktree nil)
+(defun archive--use-worktree-p ()
+ (unless archive--use-worktree
+ (setq archive--use-worktree
+ (list
+ (ignore-errors
+ (zerop (call-process "git" nil nil nil "worktree" "list"))))))
+ (car archive--use-worktree))
+
+(defun archive--external-package-sync (name)
+ "Sync external package named NAME."
+ (let ((default-directory (expand-file-name "packages/")))
+ (cond ((not (file-exists-p name))
+ (let* ((branch (concat "externals/" name))
+ (output
+ (with-temp-buffer
+ (if (archive--use-worktree-p)
+ (archive-call t "git" "worktree" "add"
+ "-B" branch
+ name (concat "origin/" branch))
+ (archive-call t "git" "clone"
+ "--reference" ".." "--single-branch"
+ "--branch" branch
+ archive--elpa-git-url name))
+ (buffer-string))))
+ (message "Cloning branch %s:\n%s" name output)))
+ ((not (file-exists-p (concat name "/.git")))
+ (message "%s is in the way of an external, please remove!" name))
+ (t (archive--pull name)))))
+
+(defun archive--core-package-empty-dest-p (dest)
+ "Return non-nil if DEST is an empty variant."
+ (member dest (list "" "." nil)))
+
+(defun archive--core-package-link-file
+ (source dest emacs-repo-root package-root exclude-regexp)
+ "Link file from SOURCE to DEST ensuring subdirectories."
+ (unless (string-match-p exclude-regexp source)
+ (let* ((absolute-package-file-name
+ (if (equal "" dest)
+ ;; Calling expand-file-name would remove the trailing / !
+ package-root
+ (expand-file-name dest package-root)))
+ (absolute-core-file-name
+ (expand-file-name source emacs-repo-root))
+ (directory (file-name-directory absolute-package-file-name)))
+ (when (fboundp 'file-name-quote) ;Not yet available on elpa.gnu.org
+ (setq directory (file-name-quote directory)))
+ (unless (file-directory-p directory)
+ (make-directory directory t))
+ (condition-case err
+ (make-symbolic-link absolute-core-file-name
+ absolute-package-file-name t)
+ (file-error
+ (message "Error: can't symlink to %S from %S:\n %S"
+ absolute-core-file-name absolute-package-file-name err)
+ (copy-file absolute-core-file-name
+ (if (file-directory-p absolute-package-file-name)
+ (file-name-as-directory absolute-package-file-name)
+ absolute-package-file-name)))))
+ (message " %s -> %s" source (if (archive--core-package-empty-dest-p dest)
+ (file-name-nondirectory source)
+ dest))))
+
+(defun archive--core-package-link-directory
+ (source dest emacs-repo-root package-root exclude-regexp)
+ "Link directory files from SOURCE to DEST ensuring subdirectories."
+ (let ((stack (list source))
+ (base source)
+ (absolute-source))
+ (while stack
+ (setq source (pop stack)
+ absolute-source (expand-file-name source emacs-repo-root))
+ (if (file-directory-p absolute-source)
+ (dolist (file (directory-files absolute-source))
+ (unless (member file (list "." ".."))
+ (push (concat (file-name-as-directory source) file) stack)))
+ (let* ((base (file-name-as-directory base))
+ (source-sans-base (substring source (length base)))
+ (package-file-name
+ (if (archive--core-package-empty-dest-p dest)
+ ;; Link to root with its original filename.
+ source-sans-base
+ (concat
+ ;; Prepend the destination, allowing for directory rename.
+ (file-name-as-directory dest) source-sans-base))))
+ (archive--core-package-link-file
+ source package-file-name
+ emacs-repo-root package-root exclude-regexp))))))
+
+(defun archive--core-package-sync (definition)
+ "Sync core package from DEFINITION."
+ (pcase-let*
+ ((`(,name . (:core ,file-patterns :excludes ,excludes)) definition)
+ (emacs-repo-root (expand-file-name "emacs"))
+ (package-root (file-name-as-directory
+ (expand-file-name name "packages")))
+ (default-directory package-root)
+ (exclude-regexp
+ (mapconcat #'identity
+ (mapcar #'wildcard-to-regexp
+ (append '("*.elc" "*~") excludes nil))
+ "\\|"))
+ (file-patterns
+ (mapcar
+ (lambda (file-pattern)
+ (pcase file-pattern
+ ((pred (stringp)) (cons file-pattern ""))
+ (`(,file ,dest . ,_) (cons file dest))
+ (_ (error "Unrecognized file format for package %s: %S"
+ name file-pattern))))
+ (if (stringp file-patterns)
+ ;; Files may be just a string, normalize.
+ (list file-patterns)
+ file-patterns))))
+ (message "Linking files for package: %s" name)
+ (when (file-directory-p package-root)
+ (delete-directory package-root t))
+ (make-directory package-root t)
+ (dolist (file-pattern file-patterns)
+ (pcase-let* ((`(,file . ,dest) file-pattern))
+ (if (file-directory-p (expand-file-name file emacs-repo-root))
+ (archive--core-package-link-directory
+ file dest emacs-repo-root package-root exclude-regexp)
+ (archive--core-package-link-file
+ file dest emacs-repo-root package-root exclude-regexp))))))
+
+(defun archive-add/remove/update-externals ()
+ "Remove non-package directories and fetch external packages."
+ (let ((externals-list (archive--read-externals-list)))
+ (let ((with-core (archive--sync-emacs-repo)))
+ (archive--cleanup-packages externals-list with-core)
+ (pcase-dolist ((and definition `(,name ,kind ,_url)) externals-list)
+ (pcase kind
+ (`:subtree nil) ;Nothing to do.
+ (`:external (archive--external-package-sync name))
+ (`:core (when with-core (archive--core-package-sync definition)))
+ (_ (message "Unknown external package kind `%S' for %s"
+ kind name)))))))
+
+;;; Manage .gitignore
+
+(defun archive-gitignore-externals (elf gf)
+ (let ((pkgs (cl-loop
+ for (name kind . _) in (archive--read-externals-list
+ (file-name-directory elf))
+ when (memq kind '(:external :core))
+ collect name)))
+ (with-current-buffer (find-file-noselect gf)
+ (goto-char (point-min))
+ (when (re-search-forward
+ "#.*External.*git.*\n\\(packages/[^*/\n]+/?\n\\)+"
+ nil 'move)
+ (replace-match ""))
+ (insert "# External packages with their own .git tree [autogenerated].\n"
+ (mapconcat (lambda (p) (format "packages/%s/\n" p))
+ (sort pkgs #'string<)
+ ""))
+ (save-buffer))))
+
+(provide 'archive-contents)
+;;; archive-contents.el ends here
diff --git a/admin/ert-support.el b/admin/ert-support.el
new file mode 100644
index 0000000..a0ff9ab
--- /dev/null
+++ b/admin/ert-support.el
@@ -0,0 +1,55 @@
+;; The contents of this file are subject to the GPL License, Version 3.0.
+
+;; Copyright (C) 2016-2017, Free Software Foundation, Inc.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+(defun ert-support-package-install (top-directory package)
+ ;; blitz default value and set up from elpa.
+ (setq package-archives
+ `(("local-elpa"
+ . ,(expand-file-name "archive/packages" top-directory)))
+ package-user-dir (make-temp-file "elpa-test" t))
+ (package-initialize)
+ (package-refresh-contents)
+ (package-install package))
+
+(defun ert-support-test-find-tests (package-directory package)
+ (append
+ `(,(expand-file-name
+ (concat (symbol-name package) "-autoloads.el") package-directory))
+ (or
+ (directory-files package-directory t ".*-test.el$")
+ (directory-files package-directory t ".*-tests.el$")
+ (let ((dir-test (expand-file-name "test" package-directory)))
+ (when (file-directory-p dir-test)
+ (directory-files dir-test t directory-files-no-dot-files-regexp)))
+ (let ((dir-tests (expand-file-name "tests" package-directory)))
+ (when (file-directory-p dir-tests)
+ (directory-files dir-tests t directory-files-no-dot-files-regexp))))))
+
+(defun ert-support-load-tests (package-directory package)
+ (mapc
+ (lambda (file)
+ (let ((force-load-messages t))
+ (load-file file)))
+ (ert-support-test-find-tests package-directory package)))
+
+(defun ert-support-test-package (top-directory package)
+ (ert-support-package-install top-directory package)
+ (ert-support-load-tests
+ (expand-file-name (concat "packages/" (symbol-name package)) top-directory)
+ package)
+
+ (ert-run-tests-batch-and-exit t))
diff --git a/admin/forward-diffs.py b/admin/forward-diffs.py
new file mode 100755
index 0000000..c0c330d
--- /dev/null
+++ b/admin/forward-diffs.py
@@ -0,0 +1,438 @@
+#!/usr/bin/python
+### forward-diffs.py --- forward emacs-diffs mails to maintainers
+
+## Copyright (C) 2012-2014 Free Software Foundation, Inc.
+
+## Author: Glenn Morris <rgm@gnu.org>
+## Maintainer: emacs-devel@gnu.org
+
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+### Commentary:
+
+## Forward emails from an emacs-diffs style mailing list to the
+## maintainer(s) of the modified files.
+
+## Two modes of operation:
+
+## 1) Create the maintfile (really this is just an optimization):
+## forward-diffs.py --create -p packagesdir -m maintfile
+
+## You can start with an empty maintfile and normal operation in 2)
+## will append information as needed.
+
+## 2) Call from eg procmail to forward diffs. Example usage:
+
+## :0c
+## * ^TO_emacs-elpa-diffs@gnu\.org
+## | forward-diffs.py -p packagedir -m maintfile -l logfile \
+## -o overmaint -s sender
+
+## where
+
+## packagedir = /path/to/packages
+## sender = your email address
+## logfile = file to write log to (you might want to rotate/compress/examine
it)
+## maintfile = file listing files and their maintainers, with format:
+##
+## package1/file1 email1
+## package2/file2 email2,email3
+## package3 email4
+##
+## Use "nomail" for the email field to not send a mail.
+## An entry that is a directory applies to all files in that directory
+## that do not have specific maintainers.
+##
+## overmaint = like maintfile, but takes precedence over it.
+
+### Code:
+
+import optparse
+import sys
+import re
+import email
+import smtplib
+import datetime
+import os
+
+
+## Scan FILE for Author or Maintainer (preferred) headers.
+## Return a list of all email addresses found in MAINTS.
+def scan_file(file, maints):
+
+ try:
+ fd = open( file, 'r')
+ except Exception as err:
+ lfile.write('Error opening file %s: %s\n' % (file, str(err)))
+ return 1
+
+ ## Max number of lines to scan looking for a maintainer.
+ ## (20 seems to be the highest at present).
+ max_lines = 50
+ nline = 0
+ cont = 0
+ type = ""
+
+ for line in fd:
+
+ nline += 1
+
+ if ( nline > max_lines ): break
+
+ ## Try and de-obfuscate. Worth it?
+ line = re.sub( '(?i) AT ', '@', line )
+ line = re.sub( '(?i) DOT ', '.', line )
+
+ if cont: # continued header?
+ reg = re.match( ('%s[ \t]+[^:]*?<?([\w.-]+@[\w.-]+)>?' % prefix),
line, re.I )
+ if not reg: # not a continued header
+ cont = 0
+ prefix = ""
+ if ( type == "maint" ): break
+ type = ""
+
+ ## Check for one header immediately after another.
+ if not cont:
+ reg = re.match( '([^ ]+)? *(Author|Maintainer)s?:
.*?<?([\w.-]+@[\w.-]+)>?', line, re.I )
+
+
+ if not reg: continue
+
+ if cont:
+ email = reg.group(1)
+ maints.append(email)
+ else:
+ cont = 1
+ prefix = reg.group(1) or ""
+ type = reg.group(2)
+ email = reg.group(3)
+ type = "maint" if re.search( 'Maintainer', type, re.I ) else "auth"
+ ## maints = [] does the wrong thing.
+ if type == "maint": del maints[:]
+ maints.append(email)
+
+ fd.close()
+
+
+## Scan all the files under dir for maintainer information.
+## Write to stdout, or optional argument outfile (which is overwritten).
+def scan_dir(dir, outfile=None):
+
+ dir = re.sub( '/+$', '', dir) + '/' # ensure trailing /
+
+ if not os.path.isdir(dir):
+ sys.stderr.write('No such directory: %s\n' % dir)
+ sys.exit(1)
+
+ fd = 0
+ if outfile:
+ try:
+ fd = open( outfile, 'w' )
+ except Exception as err:
+ sys.stderr.write("Error opening `%s': %s\n" % (outfile, str(err)))
+ sys.exit(1)
+
+
+ for dirpath, dirnames, filenames in os.walk(dir):
+ for file in filenames:
+ path = os.path.join(dirpath, file)
+ maints = []
+ scan_file(path, maints)
+ ## This would skip printing empty maints.
+ ## That would mean we would scan the file each time for no reason.
+ ## But empty maintainers are an error at present.
+ if not maints: continue
+ path = re.sub( '^%s' % dir, '', path )
+ string = "%-50s %s\n" % (path, ",".join(maints))
+ if fd:
+ fd.write(string)
+ else:
+ print string,
+
+ if fd: fd.close()
+
+
+usage="""usage: %prog <-p /path/to/packages> <-m maintfile>
+ <-l logfile -s sender|--create> [-o overmaintfile] [--prefix prefix]
+ [--sendmail] [--debug]
+Take an emacs-diffs mail on stdin, and forward it to the maintainer(s)."""
+
+parser = optparse.OptionParser()
+parser.set_usage ( usage )
+parser.add_option( "-m", dest="maintfile", default=None,
+ help="file listing packages and maintainers")
+parser.add_option( "-l", dest="logfile", default=None,
+ help="file to append output to")
+parser.add_option( "-o", dest="overmaintfile", default=None,
+ help="override file listing packages and maintainers")
+parser.add_option( "-p", dest="packagedir", default=None,
+ help="path to packages directory")
+parser.add_option( "-s", dest="sender", default=None,
+ help="sender address for forwards")
+parser.add_option( "--create", dest="create", default=False,
+ action="store_true", help="create maintfile")
+parser.add_option( "--no-scan", dest="noscan", default=True,
+ action="store_true",
+ help="don't scan for maintainers; implies --no-update")
+parser.add_option( "--no-update", dest="noupdate", default=False,
+ action="store_true",
+ help="do not update the maintfile")
+parser.add_option( "--prefix", dest="prefix", default="packages/",
+ help="prefix to remove from modified file name [default:
%default]")
+parser.add_option( "--sendmail", dest="sendmail", default=False,
+ action="store_true", help="use sendmail rather than smtp")
+parser.add_option( "--debug", dest="debug", default=False,
+ action="store_true", help="debug only, do not send mail")
+
+
+( opts, args ) = parser.parse_args()
+
+
+if not opts.maintfile:
+ parser.error('No maintfile specified')
+
+if not opts.packagedir:
+ parser.error('No packagedir specified')
+
+if not os.path.isdir(opts.packagedir):
+ sys.stderr.write('No such directory: %s\n' % opts.packagedir)
+ sys.exit(1)
+
+
+if not opts.create:
+ if not opts.logfile:
+ parser.error('No logfile specified')
+
+ if not opts.sender:
+ parser.error('No sender specified')
+
+
+try:
+ lfile = open( opts.logfile, 'a' )
+except Exception as err:
+ sys.stderr.write('Error opening logfile: %s\n' % str(err))
+ sys.exit(1)
+
+
+try:
+ mfile = open( opts.maintfile, 'r' )
+except Exception as err:
+ lfile.write('Error opening maintfile: %s\n' % str(err))
+ sys.exit(1)
+
+## Create the maintfile.
+if opts.create:
+ scan_dir( opts.packagedir, opts.maintfile )
+ sys.exit()
+
+
+## Each element is package/file: maint1, maint2, ...
+maints = {}
+
+for line in mfile:
+ if re.match( '#| *$', line ): continue
+ ## FIXME error here if empty maintainer.
+ (pfile, maint) = line.split()
+ maints[pfile] = maint.split(',')
+
+mfile.close()
+
+
+if opts.overmaintfile:
+ try:
+ ofile = open( opts.overmaintfile, 'r' )
+ except Exception as err:
+ lfile.write('Error opening overmaintfile: %s\n' % str(err))
+ sys.exit(1)
+
+ for line in ofile:
+ if re.match( '#| *$', line ): continue
+ (pfile, maint) = line.split()
+ maints[pfile] = maint.split(',')
+
+ ofile.close()
+
+
+stdin = sys.stdin
+
+text = stdin.read()
+
+
+resent_via = 'GNU Emacs diff forwarder'
+
+message = email.message_from_string( text )
+
+(msg_name, msg_from) = email.utils.parseaddr( message['from'] )
+
+lfile.write('\nDate: %s\n' % str(datetime.datetime.now()))
+lfile.write('Message-ID: %s\n' % message['message-id'])
+lfile.write('From: %s\n' % msg_from)
+
+if resent_via == message['x-resent-via']:
+ lfile.write('Mail loop; aborting\n')
+ sys.exit(1)
+
+
+start = False
+pfiles_seen = []
+maints_seen = []
+
+for line in text.splitlines():
+
+ # Look for and process things that look like (Git):
+ #
+ # Summary of changes:
+ # packages/vlf/vlf.el | 2 +-
+ # 1 files changed, 1 insertions(+), 1 deletions(-)
+ #
+ # or things that look like (Git):
+ #
+ # ---
+ # packages/vlf/vlf.el | 2 +-
+ # 1 files changed, 1 insertions(+), 1 deletions(-)
+
+ #BZR: if re.match( 'modified:$', line ):
+ if re.match( '---|Summary of changes:$', line ):
+ start = True
+ continue
+
+ if not start: continue
+
+ ## An empty line or a line with non-empty first character.
+ if re.match( '( *$|[^ ])', line ): break
+ # Any line that doesn't match the diffstat format (Git).
+ if not re.match( ' [^ ]+ +\| ', line ):
+ lfile.write('Stop scanning at: %s\n' % line)
+ break
+
+ if opts.prefix:
+ #BZR: reg = re.match( '%s([^ ]+)' % opts.prefix, line.strip() )
+ reg = re.match( ' %s([^ ]+)' % opts.prefix, line )
+ if not reg:
+ lfile.write('Skip: %s\n' % line)
+ continue
+ pfile = reg.group(1)
+ else:
+ pfile = line.strip()
+
+
+ lfile.write('File: %s\n' % pfile)
+
+ ## Should not be possible for files (rather than packages)...
+ if pfile in pfiles_seen:
+ lfile.write('Already seen this file\n')
+ continue
+
+ pfiles_seen.append(pfile)
+
+
+ if not pfile in maints:
+
+ lfile.write('Unknown maintainer\n')
+
+ if not opts.noscan:
+
+ lfile.write('Scanning file...\n')
+ thismaint = []
+ thisfile = os.path.join( opts.packagedir, pfile )
+ # scan_file( thisfile, thismaint )
+
+ if thismaint:
+ maints[pfile] = thismaint
+
+ ## Append maintainer to file.
+ if not opts.noupdate:
+ try:
+ mfile = open( opts.maintfile, 'a' )
+ string = "%-50s %s\n" % (pfile, ",".join(thismaint))
+ mfile.write(string)
+ mfile.close()
+ lfile.write('Appended to maintfile\n')
+ except Exception as err:
+ lfile.write('Error appending to maintfile: %s\n' %
+ str(err))
+
+ ## Didn't scan, or scanning did not work.
+ ## Look for a directory maintainer.
+ if not pfile in maints:
+ lfile.write('No file maintainer, trying directories...\n')
+ while True:
+ (pfile, tail) = os.path.split(pfile)
+ if not pfile: break
+ if pfile in maints: break
+
+
+ if not pfile in maints:
+ lfile.write('No maintainer, skipping\n')
+ continue
+
+
+ for maint in maints[pfile]:
+
+ lfile.write('Maint: %s\n' % maint)
+
+
+ if maint in maints_seen:
+ lfile.write('Already seen this maintainer\n')
+ continue
+
+ maints_seen.append(maint)
+
+
+ if maint == "nomail":
+ lfile.write('Not resending, no mail is requested\n')
+ continue
+
+
+ if maint == msg_from:
+ lfile.write('Not resending, since maintainer = committer\n')
+ continue
+
+
+ forward = message
+ forward.add_header('X-Resent-Via', resent_via)
+ forward.add_header('Resent-To', maint)
+ forward.add_header('Resent-From', opts.sender)
+
+ lfile.write('Resending via %s...\n' % ('sendmail'
+ if opts.sendmail else 'smtp') )
+
+
+ if opts.debug: continue
+
+
+ if opts.sendmail:
+ s = os.popen("/usr/sbin/sendmail -i -f %s %s" %
+ (opts.sender, maint), "w")
+ s.write(forward.as_string())
+ status = s.close()
+ if status:
+ lfile.write('Sendmail exit status: %s\n' % status)
+
+ else:
+
+ try:
+ s = smtplib.SMTP('localhost')
+ except Exception as err:
+ lfile.write('Error opening smtp: %s\n' % str(err))
+ sys.exit(1)
+
+ try:
+ s.sendmail(opts.sender, maint, forward.as_string())
+ except Exception as err:
+ lfile.write('Error sending smtp: %s\n' % str(err))
+
+ s.quit()
+
+### forward-diffs.py ends here
diff --git a/admin/hv.sh b/admin/hv.sh
new file mode 100644
index 0000000..9a45c92
--- /dev/null
+++ b/admin/hv.sh
@@ -0,0 +1,47 @@
+# hv.sh
+#
+# Author: Thien-Thi Nguyen <ttn@gnu.org>
+# License: Public Domain
+##
+# Usage: version=VERSION ; . hv.sh
+#
+# This file is not executable. Instead, it is meant
+# to be sourced (i.e., "." in sh, or "source" in bash).
+#
+# It sets shell variable ‘me’ to the basename(1) of $0,
+# then checks $1 for either ‘--help’ or ‘--version’.
+#
+# If ‘--help’, it scans $0 for the flush-left comment block w/ form:
+# ##
+# # HELP-TEXT
+# # ...
+# ##
+# formats it to stdout, and exits successfully (status 0). More precisely,
+# the first and last comment lines are ‘##’ (double-hash) and are omitted,
+# as are the ‘#’ (hash) at the beginning of each line. HELP-TEXT can be
+# multiline, including blank lines. It's customary to start HELP-TEXT w/
+# "Usage:" or "Synopsis:", like a manpage, but that is not required.
+#
+# If $1 is ‘--version’, this file displays to stdout
+# PROGNAME VERSION
+# and exits successfully (status 0). PROGNAME and VERSION are the values
+# of the ‘me’ and ‘version’ shell variables, respectively. This is why
+# ‘version’ must be set prior to sourcing the file. If ‘version’ is not
+# set or is the empty string, display "VERSION UNKNOWN" for VERSION.
+#
+# Any other value of $1 is silently ignored.
+##
+
+me=`basename "$0"`
+
+if [ x"$1" = x--help ] ; then
+ sed '/^##/,/^##/!d;/^##/d;s/^# //g;s/^#$//g' "$0"
+ exit 0
+fi
+
+if [ x"$1" = x--version ] ; then
+ echo "$me" "${version:-VERSION UNKNOWN}"
+ exit 0
+fi
+
+# hv.sh ends here
diff --git a/admin/update-archive.sh b/admin/update-archive.sh
new file mode 100755
index 0000000..6560748
--- /dev/null
+++ b/admin/update-archive.sh
@@ -0,0 +1,235 @@
+#!/bin/sh
+
+# TODO: Author
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+##
+# Usage: update-archive.sh [options]
+#
+# Update the archive. This involves several steps,
+# some performed in the "buildir" (cwd at invocation),
+# which should be a sibling of the elpa/ dir.
+#
+# Options:
+# --announce EMAIL -- also send announcement to EMAIL address
+# --batch -- write std{out,err} to make.log (in buildir)
+#
+# Preconditions:
+# - Installed software: /usr/sbin/sendmail, git, rsync, make, emacs.
+# - Internet connection (for ‘git pull’, sending mail).
+# - There should be a sibling directory of elpa/: staging/.
+#
+# Gory operation details follow (for maintainers).
+#
+# * Flow (see Cahoots for ‘[N]’)
+#
+# First, in sibling dir ../elpa, fetch changes (via ‘git pull’),
+# set up and update external packages[1], and check copyrights[2].
+# Signal error if any sub-step fails.
+#
+# Back in $buildir, snapshot ../elpa/packages/* as packages/*,
+# excluding some files such as ChangeLog, .git/, *.elc, and so on;
+# refresh the ChangeLog files[3]; wipe and recreate dir archive/[4].
+# Some of these sub-steps signal error on failure.
+#
+# In $buildir/archive/, make emacs-packages-latest.tgz from subdir
+# $buildir/archive/packages/ (unpacking creates ./package/*).
+#
+# In parent dir of $buildir, ensure existence of directories
+# staging/packages/ and staging-old/ -- that is, $buildir has
+# two sibling dirs staging/ and staging-old/ -- and then snapshot
+# staging/* to staging-old/* [which all kind of implies that
+# staging/ is persistent (is not a temporary dir), right? --ttn].
+# To populate staging/packages/ (here, called ‘dst’), iterate over
+# $buildir/archive/packages/* (here, called ‘src’) and do one of:
+# (a) for */archive-contents, *-readme.txt, mv directly
+# (b) if $dst/PV already exists, delete $src/PV
+# (c) mv $src/PV $dst/PV and announce it (if ‘--announce’)
+# Afterwards, mv $buildir/archive/emacs-packages-latest.tgz to staging/
+# and delete $buildir/archive/ (and all its subdirs).
+#
+# Lastly, in ../staging/packages/, make the HTML and readme.txt files[5].
+#
+# * Cahoots
+#
+# These programs are in cahoots w/ update-archive.sh -- here,
+# "lisp" means Emacs Lisp function found in archive-contents.el,
+# and "make" means makefile target found in ../GNUmakefile.
+# [1] lisp ‘archive-add/remove/update-externals’
+# [2] make ‘check_copyrights’
+# [3] lisp ‘archive-prepare-packages’
+# [4] make ‘archive-full’
+# [5] lisp ‘batch-html-make-index’
+#
+# * Miscellaneous
+#
+# "Signal error" means report an error and exit w/ status 1.
+# If invoked w/ ‘--batch’, reporting means mailing the log file
+# to emacs-elpa-diffs (a gnu dot org mailing list) using the
+# error message as title. Otherwise, reporting means displaying
+# the error message to stdout.
+#
+# Mail sender (From) is "ELPA update" w/ bogus address.
+#
+# "Snapshot" means use ‘rsync -av’ (plus other options).
+##
+# [NB: I inferred these from VCS logs. Corrections welcome! --ttn]
+# 0.x -- release from the previous VCS
+# 1.0 -- initial release from this VCS (Git)
+# 1.1 -- add ‘--announce EMAIL’ support
+# 1.2 -- fix externals maintenance
+# 1.3 -- fix ‘--announce EMAIL’ support
+# 1.4 -- use sendmail(8) and rsync(1)
+# 1.5 -- make staging operations less brittle
+# 1.6 -- support ‘--help’, ‘--version’
+# 1.7 -- fix DANGEROUS bug; make less noisy; name bash explicitly
+# 1.8 -- revert "name bash explicitly"
+version='1.8'
+# If $0 is a symlink, `dirname $0`/hv.sh might not be available,
+# and even if it IS available, how can we be sure it's bonafide?
+test -L "$0" || { hv=`dirname "$0"`/hv.sh ; test -r "$hv" && . "$hv" ; }
+
+# TODO: (here) Validate args.
+
+set -x
+
+makelog=""
+buildir="$(pwd)"
+
+announce=no
+a_email="" #info-gnu-emacs@gnu.org
+
+export LANG=C
+while [ $# -gt 0 ]; do
+ case "$1" in
+ "--announce") announce=yes; a_email="$2"; shift ;;
+ "--batch")
+ makelog="$(pwd)/make.log"
+ exec >"$makelog" 2>&1
+ ;;
+ esac
+ shift
+done
+
+send_mail () {
+ to="$1"; shift
+ title="$*"
+ (cat <<ENDDOC
+From: ELPA update <do.not.reply@elpa.gnu.org>
+To: $to
+Subject: $title
+
+ENDDOC
+ cat -) | /usr/sbin/sendmail "$to"
+}
+
+# Send an email to warn about a problem.
+signal_error () {
+ title="$*"
+ if [ "" = "$makelog" ]; then
+ echo "Error: $title"
+ else
+ send_mail "emacs-elpa-diffs@gnu.org" "$title" <"$makelog"
+ fi
+ exit 1
+}
+
+announce_new () {
+ if [ "yes" != "$announce" ]; then return; fi
+ pv="$1"
+ pkg="$(echo "$pv" | sed -e 's/^\(.*\)-\([^-]*\)\.[^-.]*$/\1/')"
+ ver="$(echo "$pv" | sed -e 's/^\(.*\)-\([^-]*\)\.[^-.]*$/\2/')"
+ if [ -z "$pkg" ] || [ -z "$ver" ]; then signal_error "bad PKG-VER: $pv"; fi
+ send_mail "$a_email" "[GNU ELPA] $pkg version $ver" <<ENDDOC
+Version $ver of GNU ELPA package $pkg has just been released.
+You can now find it in M-x package-list RET.
+
+More at http://elpa.gnu.org/packages/$pkg.html
+ENDDOC
+}
+
+cd ../elpa || exit
+
+# Fetch changes.
+git pull || signal_error "git pull failed"
+
+# Remember we're inside the "elpa" branch which we don't want to trust,
+# So always refer to the makefile and admins files from $builddir".
+
+# Setup and update externals.
+emacs --batch -l "$buildir/admin/archive-contents.el" \
+ -f archive-add/remove/update-externals
+
+make -f "$buildir/GNUmakefile" check_copyrights ||
+ signal_error "check_copyright failed"
+
+cd "$buildir" || exit
+
+rsync -av --delete \
+ --exclude=ChangeLog \
+ --exclude=.git \
+ --exclude='*.elc' \
+ --exclude='*~' \
+ --exclude='/*/*/*-autoloads.el' \
+ ../elpa/packages ./
+
+# Refresh the ChangeLog files. This needs to be done in
+# the source tree, because it needs the VCS data!
+emacs -batch -l admin/archive-contents.el \
+ -eval '(archive-prepare-packages "../elpa")'
+
+
+rm -rf archive # In case there's one left over!
+make archive-full || {
+ signal_error "make archive-full failed"
+}
+latest="emacs-packages-latest.tgz"
+(cd archive || exit
+ GZIP=--best tar zcf "$latest" packages)
+(cd ../
+ mkdir -p staging/packages
+ # Not sure why we have `staging-old', but let's keep it for now.
+ mkdir -p staging-old
+ rsync -av --inplace --delete staging/. staging-old/.
+ # Move new files into place but don't throw out old package versions.
+ for f in "$buildir"/archive/packages/*; do
+ # PKG-VER
+ pv=$(basename "$f")
+ dst="staging/packages/$pv"
+ # Actually, let's never overwrite an existing version. So changes can
+ # be installed without causing a new package to be built until the
+ # version field is changed. Some files need to be excluded from the
+ # "immutable" policy, most importantly "archive-contents"
+ # and "*-readme.txt".
+ case $dst in
+ */archive-contents | *-readme.txt ) mv "$f" "$dst" ;;
+ * ) if [ -r "$dst" ]
+ then rm "$f"
+ else
+ mv "$f" "$dst"
+ # FIXME: Add a tag to remember the precise code used.
+ announce_new "$pv"
+ fi ;;
+ esac
+ done
+ mv "$buildir"/archive/"$latest" staging/
+ rm -rf "$buildir"/archive)
+
+# Make the HTML and readme.txt files.
+(cd ../staging/packages || exit
+ emacs --batch -l "$buildir"/admin/archive-contents.el \
+ --eval '(batch-html-make-index)')
+
+# update-archive.sh ends here