comparison vext.sml @ 1756:edaa018a3731

Update Vext
author Chris Cannam
date Mon, 11 Dec 2017 08:13:32 +0000
parents 716e13004b19
children 42d57c382e56
comparison
equal deleted inserted replaced
1755:c866f9a77b23 1756:edaa018a3731
36 shall not be used in advertising or otherwise to promote the sale, 36 shall not be used in advertising or otherwise to promote the sale,
37 use or other dealings in this Software without prior written 37 use or other dealings in this Software without prior written
38 authorization. 38 authorization.
39 *) 39 *)
40 40
41 val vext_version = "0.9.91" 41 val vext_version = "0.9.92"
42 42
43 43
44 datatype vcs = 44 datatype vcs =
45 HG | 45 HG |
46 GIT 46 GIT |
47 SVN
47 48
48 datatype source = 49 datatype source =
49 URL_SOURCE of string | 50 URL_SOURCE of string |
50 SERVICE_SOURCE of { 51 SERVICE_SOURCE of {
51 service : string, 52 service : string,
172 173
173 (** Check out, i.e. clone a fresh copy of, the repo for the given 174 (** Check out, i.e. clone a fresh copy of, the repo for the given
174 library on the given branch *) 175 library on the given branch *)
175 val checkout : context -> libname * source * branch -> unit result 176 val checkout : context -> libname * source * branch -> unit result
176 177
177 (** Update the library to the given branch tip *) 178 (** Update the library to the given branch tip. Assumes that a
179 local copy of the library already exists. Return the new id *)
178 val update : context -> libname * source * branch -> id_or_tag result 180 val update : context -> libname * source * branch -> id_or_tag result
179 181
180 (** Update the library to the given specific id or tag *) 182 (** Update the library to the given specific id or tag *)
181 val update_to : context -> libname * source * id_or_tag -> id_or_tag result 183 val update_to : context -> libname * source * id_or_tag -> id_or_tag result
184
185 (** Return a URL from which the library can be cloned, given that
186 the local copy already exists. For a DVCS this can be the
187 local copy, but for a centralised VCS it will have to be the
188 remote repository URL. Used for archiving *)
189 val copy_url_for : context -> libname -> string result
182 end 190 end
183 191
184 signature LIB_CONTROL = sig 192 signature LIB_CONTROL = sig
185 val review : context -> libspec -> (libstate * localstate) result 193 val review : context -> libspec -> (libstate * localstate) result
186 val status : context -> libspec -> (libstate * localstate) result 194 val status : context -> libspec -> (libstate * localstate) result
192 val extpath : context -> string 200 val extpath : context -> string
193 val libpath : context -> libname -> string 201 val libpath : context -> libname -> string
194 val subpath : context -> libname -> string -> string 202 val subpath : context -> libname -> string -> string
195 val command_output : context -> libname -> string list -> string result 203 val command_output : context -> libname -> string list -> string result
196 val command : context -> libname -> string list -> unit result 204 val command : context -> libname -> string list -> unit result
205 val file_url : string -> string
197 val file_contents : string -> string 206 val file_contents : string -> string
198 val mydir : unit -> string 207 val mydir : unit -> string
199 val homedir : unit -> string 208 val homedir : unit -> string
200 val mkpath : string -> unit result 209 val mkpath : string -> unit result
201 val rmpath : string -> unit result 210 val rmpath : string -> unit result
211 val nonempty_dir_exists : string -> bool
202 val project_spec_path : string -> string 212 val project_spec_path : string -> string
203 val project_lock_path : string -> string 213 val project_lock_path : string -> string
204 val verbose : unit -> bool 214 val verbose : unit -> bool
205 end = struct 215 end = struct
206 216
258 fun project_lock_path rootpath = 268 fun project_lock_path rootpath =
259 project_file_path rootpath (VextFilenames.project_lock_file) 269 project_file_path rootpath (VextFilenames.project_lock_file)
260 270
261 fun trim str = 271 fun trim str =
262 hd (String.fields (fn x => x = #"\n" orelse x = #"\r") str) 272 hd (String.fields (fn x => x = #"\n" orelse x = #"\r") str)
273
274 fun file_url path =
275 let val forward_path =
276 String.translate (fn #"\\" => "/" |
277 c => Char.toString c)
278 (OS.Path.mkCanonical path)
279 in
280 (* Path is expected to be absolute already, but if it
281 starts with a drive letter, we'll need an extra slash *)
282 case explode forward_path of
283 #"/"::rest => "file:///" ^ implode rest
284 | _ => "file:///" ^ forward_path
285 end
263 286
264 fun file_contents filename = 287 fun file_contents filename =
265 let val stream = TextIO.openIn filename 288 let val stream = TextIO.openIn filename
266 fun read_all str acc = 289 fun read_all str acc =
267 case TextIO.inputLine str of 290 case TextIO.inputLine str of
348 fun command_output context libname cmdlist = 371 fun command_output context libname cmdlist =
349 let open OS 372 let open OS
350 val tmpFile = FileSys.tmpName () 373 val tmpFile = FileSys.tmpName ()
351 val result = run_command context libname cmdlist (SOME tmpFile) 374 val result = run_command context libname cmdlist (SOME tmpFile)
352 val contents = file_contents tmpFile 375 val contents = file_contents tmpFile
376 val _ = if verbose ()
377 then print ("Output was:\n\"" ^ contents ^ "\"\n")
378 else ()
353 in 379 in
354 FileSys.remove tmpFile handle _ => (); 380 FileSys.remove tmpFile handle _ => ();
355 case result of 381 case result of
356 OK () => OK contents 382 OK () => OK contents
357 | ERROR e => ERROR e 383 | ERROR e => ERROR e
393 ERROR ("Directory creation failed: " ^ e)) 419 ERROR ("Directory creation failed: " ^ e))
394 420
395 fun mkpath path = 421 fun mkpath path =
396 mkpath' (OS.Path.mkCanonical path) 422 mkpath' (OS.Path.mkCanonical path)
397 423
398 fun rmpath' path = 424 fun dir_contents dir =
399 let open OS 425 let open OS
400 fun files_from dirstream = 426 fun files_from dirstream =
401 case FileSys.readDir dirstream of 427 case FileSys.readDir dirstream of
402 NONE => [] 428 NONE => []
403 | SOME file => 429 | SOME file =>
404 (* readDir is supposed to filter these, 430 (* readDir is supposed to filter these,
405 but let's be extra cautious: *) 431 but let's be extra cautious: *)
406 if file = Path.parentArc orelse file = Path.currentArc 432 if file = Path.parentArc orelse file = Path.currentArc
407 then files_from dirstream 433 then files_from dirstream
408 else file :: files_from dirstream 434 else file :: files_from dirstream
409 fun contents dir = 435 val stream = FileSys.openDir dir
410 let val stream = FileSys.openDir dir 436 val files = map (fn f => Path.joinDirFile
411 val files = map (fn f => Path.joinDirFile 437 { dir = dir, file = f })
412 { dir = dir, file = f }) 438 (files_from stream)
413 (files_from stream) 439 val _ = FileSys.closeDir stream
414 val _ = FileSys.closeDir stream 440 in
415 in files 441 files
416 end 442 end
443
444 fun rmpath' path =
445 let open OS
417 fun remove path = 446 fun remove path =
418 if FileSys.isLink path (* dangling links bother isDir *) 447 if FileSys.isLink path (* dangling links bother isDir *)
419 then FileSys.remove path 448 then FileSys.remove path
420 else if FileSys.isDir path 449 else if FileSys.isDir path
421 then (app remove (contents path); FileSys.rmDir path) 450 then (app remove (dir_contents path); FileSys.rmDir path)
422 else FileSys.remove path 451 else FileSys.remove path
423 in 452 in
424 (remove path; OK ()) 453 (remove path; OK ())
425 handle SysErr (e, _) => ERROR ("Path removal failed: " ^ e) 454 handle SysErr (e, _) => ERROR ("Path removal failed: " ^ e)
426 end 455 end
427 456
428 fun rmpath path = 457 fun rmpath path =
429 rmpath' (OS.Path.mkCanonical path) 458 rmpath' (OS.Path.mkCanonical path)
430 459
460 fun nonempty_dir_exists path =
461 let open OS.FileSys
462 in
463 (not (isLink path) andalso
464 isDir path andalso
465 dir_contents path <> [])
466 handle _ => false
467 end
468
431 end 469 end
432 470
433 functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct 471 functor LibControlFn (V: VCS_CONTROL) :> LIB_CONTROL = struct
434 472
435 (* Valid states for unpinned libraries: 473 (* Valid states for unpinned libraries:
1037 } 1075 }
1038 } 1076 }
1039 ] 1077 ]
1040 1078
1041 fun vcs_name vcs = 1079 fun vcs_name vcs =
1042 case vcs of GIT => "git" | 1080 case vcs of HG => "hg"
1043 HG => "hg" 1081 | GIT => "git"
1082 | SVN => "svn"
1044 1083
1045 fun vcs_from_name name = 1084 fun vcs_from_name name =
1046 case name of "git" => GIT 1085 case name of "hg" => HG
1047 | "hg" => HG 1086 | "git" => GIT
1087 | "svn" => SVN
1048 | other => raise Fail ("Unknown vcs name \"" ^ name ^ "\"") 1088 | other => raise Fail ("Unknown vcs name \"" ^ name ^ "\"")
1049 1089
1050 fun load_more_providers previously_loaded json = 1090 fun load_more_providers previously_loaded json =
1051 let open JsonBits 1091 let open JsonBits
1052 fun load pjson pname : provider = 1092 fun load pjson pname : provider =
1173 given in the project file changes. *) 1213 given in the project file changes. *)
1174 1214
1175 type vcsstate = { id: string, modified: bool, 1215 type vcsstate = { id: string, modified: bool,
1176 branch: string, tags: string list } 1216 branch: string, tags: string list }
1177 1217
1178 val hg_args = [ "--config", "ui.interactive=true" ] 1218 val hg_args = [ "--config", "ui.interactive=true",
1219 "--config", "ui.merge=:merge" ]
1179 1220
1180 fun hg_command context libname args = 1221 fun hg_command context libname args =
1181 FileBits.command context libname ("hg" :: hg_args @ args) 1222 FileBits.command context libname ("hg" :: hg_args @ args)
1182 1223
1183 fun hg_command_output context libname args = 1224 fun hg_command_output context libname args =
1308 | ERROR e => 1349 | ERROR e =>
1309 case pull_result of 1350 case pull_result of
1310 ERROR e' => ERROR e' (* this was the ur-error *) 1351 ERROR e' => ERROR e' (* this was the ur-error *)
1311 | _ => ERROR e 1352 | _ => ERROR e
1312 end 1353 end
1313 1354
1355 fun copy_url_for context libname =
1356 OK (FileBits.file_url (FileBits.libpath context libname))
1357
1314 end 1358 end
1315 1359
1316 structure GitControl :> VCS_CONTROL = struct 1360 structure GitControl :> VCS_CONTROL = struct
1317 1361
1318 (* With Git repos we always operate in detached HEAD state. Even 1362 (* With Git repos we always operate in detached HEAD state. Even
1483 | ERROR e => 1527 | ERROR e =>
1484 case fetch_result of 1528 case fetch_result of
1485 ERROR e' => ERROR e' (* this was the ur-error *) 1529 ERROR e' => ERROR e' (* this was the ur-error *)
1486 | _ => ERROR e 1530 | _ => ERROR e
1487 end 1531 end
1532
1533 fun copy_url_for context libname =
1534 OK (FileBits.file_url (FileBits.libpath context libname))
1488 1535
1489 end 1536 end
1490 1537
1538 structure SvnControl :> VCS_CONTROL = struct
1539
1540 fun svn_command context libname args =
1541 FileBits.command context libname ("svn" :: args)
1542
1543 fun svn_command_output context libname args =
1544 FileBits.command_output context libname ("svn" :: args)
1545
1546 fun svn_command_lines context libname args =
1547 case svn_command_output context libname args of
1548 ERROR e => ERROR e
1549 | OK s => OK (String.tokens (fn c => c = #"\n" orelse c = #"\r") s)
1550
1551 fun split_line_pair line =
1552 let fun strip_leading_ws str = case explode str of
1553 #" "::rest => implode rest
1554 | _ => str
1555 in
1556 case String.tokens (fn c => c = #":") line of
1557 [] => ("", "")
1558 | first::rest =>
1559 (first, strip_leading_ws (String.concatWith ":" rest))
1560 end
1561
1562 fun svn_info_item context libname key =
1563 (* SVN 1.9 has info --show-item which is what we need, but at
1564 this point we still have 1.8 on the CI boxes so we might as
1565 well aim to support it *)
1566 case svn_command_lines context libname ["info"] of
1567 ERROR e => ERROR e
1568 | OK lines =>
1569 case List.find (fn (k, v) => k = key) (map split_line_pair lines) of
1570 NONE => ERROR ("Key \"" ^ key ^ "\" not found in output")
1571 | SOME (_, v) => OK v
1572
1573 fun exists context libname =
1574 OK (OS.FileSys.isDir (FileBits.subpath context libname ".svn"))
1575 handle _ => OK false
1576
1577 fun remote_for context (libname, source) =
1578 Provider.remote_url context SVN source libname
1579
1580 fun id_of context libname =
1581 svn_info_item context libname "Revision" (*!!! check: does svn localise this? should we ensure C locale? *)
1582
1583 fun is_at context (libname, id_or_tag) =
1584 case id_of context libname of
1585 ERROR e => ERROR e
1586 | OK id => OK (id = id_or_tag)
1587
1588 fun is_on_branch context (libname, b) =
1589 OK (b = DEFAULT_BRANCH)
1590
1591 fun is_newest context (libname, source, branch) =
1592 case svn_command_lines context libname ["status", "--show-updates"] of
1593 ERROR e => ERROR e
1594 | OK lines =>
1595 case rev lines of
1596 [] => ERROR "No result returned for server status"
1597 | last_line::_ =>
1598 case rev (String.tokens (fn c => c = #" ") last_line) of
1599 [] => ERROR "No revision field found in server status"
1600 | server_id::_ => is_at context (libname, server_id)
1601
1602 fun is_newest_locally context (libname, branch) =
1603 OK true (* no local history *)
1604
1605 fun is_modified_locally context libname =
1606 case svn_command_output context libname ["status"] of
1607 ERROR e => ERROR e
1608 | OK "" => OK false
1609 | OK _ => OK true
1610
1611 fun checkout context (libname, source, branch) =
1612 let val url = remote_for context (libname, source)
1613 val path = FileBits.libpath context libname
1614 in
1615 if FileBits.nonempty_dir_exists path
1616 then (* Surprisingly, SVN itself has no problem with
1617 this. But for consistency with other VCSes we
1618 don't allow it *)
1619 ERROR ("Refusing checkout to nonempty dir \"" ^ path ^ "\"")
1620 else
1621 (* make the lib dir rather than just the ext dir, since
1622 the lib dir might be nested and svn will happily check
1623 out into an existing empty dir anyway *)
1624 case FileBits.mkpath (FileBits.libpath context libname) of
1625 ERROR e => ERROR e
1626 | _ => svn_command context "" ["checkout", url, libname]
1627 end
1628
1629 fun update context (libname, source, branch) =
1630 case svn_command context libname
1631 ["update", "--accept", "postpone"] of
1632 ERROR e => ERROR e
1633 | _ => id_of context libname
1634
1635 fun update_to context (libname, _, "") =
1636 ERROR "Non-empty id (tag or revision id) required for update_to"
1637 | update_to context (libname, source, id) =
1638 case svn_command context libname
1639 ["update", "-r", id, "--accept", "postpone"] of
1640 ERROR e => ERROR e
1641 | OK _ => id_of context libname
1642
1643 fun copy_url_for context libname =
1644 svn_info_item context libname "URL"
1645
1646 end
1647
1491 structure AnyLibControl :> LIB_CONTROL = struct 1648 structure AnyLibControl :> LIB_CONTROL = struct
1492 1649
1493 structure H = LibControlFn(HgControl) 1650 structure H = LibControlFn(HgControl)
1494 structure G = LibControlFn(GitControl) 1651 structure G = LibControlFn(GitControl)
1652 structure S = LibControlFn(SvnControl)
1495 1653
1496 fun review context (spec as { vcs, ... } : libspec) = 1654 fun review context (spec as { vcs, ... } : libspec) =
1497 (fn HG => H.review | GIT => G.review) vcs context spec 1655 (fn HG => H.review | GIT => G.review | SVN => S.review) vcs context spec
1498 1656
1499 fun status context (spec as { vcs, ... } : libspec) = 1657 fun status context (spec as { vcs, ... } : libspec) =
1500 (fn HG => H.status | GIT => G.status) vcs context spec 1658 (fn HG => H.status | GIT => G.status | SVN => S.status) vcs context spec
1501 1659
1502 fun update context (spec as { vcs, ... } : libspec) = 1660 fun update context (spec as { vcs, ... } : libspec) =
1503 (fn HG => H.update | GIT => G.update) vcs context spec 1661 (fn HG => H.update | GIT => G.update | SVN => S.update) vcs context spec
1504 1662
1505 fun id_of context (spec as { vcs, ... } : libspec) = 1663 fun id_of context (spec as { vcs, ... } : libspec) =
1506 (fn HG => H.id_of | GIT => G.id_of) vcs context spec 1664 (fn HG => H.id_of | GIT => G.id_of | SVN => S.id_of) vcs context spec
1665
1507 end 1666 end
1508 1667
1509 1668
1510 type exclusions = string list 1669 type exclusions = string list
1511 1670
1556 does not. End users shouldn't get to see Vext) 1715 does not. End users shouldn't get to see Vext)
1557 1716
1558 - Clean up by deleting the new copy 1717 - Clean up by deleting the new copy
1559 *) 1718 *)
1560 1719
1561 fun project_vcs_and_id dir = 1720 fun project_vcs_id_and_url dir =
1562 let val context = { 1721 let val context = {
1563 rootpath = dir, 1722 rootpath = dir,
1564 extdir = ".", 1723 extdir = ".",
1565 providers = [], 1724 providers = [],
1566 accounts = [] 1725 accounts = []
1567 } 1726 }
1568 val vcs_maybe = 1727 val vcs_maybe =
1569 case [HgControl.exists context ".", 1728 case [HgControl.exists context ".",
1570 GitControl.exists context "."] of 1729 GitControl.exists context ".",
1571 [OK true, OK false] => OK HG 1730 SvnControl.exists context "."] of
1572 | [OK false, OK true] => OK GIT 1731 [OK true, OK false, OK false] => OK HG
1732 | [OK false, OK true, OK false] => OK GIT
1733 | [OK false, OK false, OK true] => OK SVN
1573 | _ => ERROR ("Unable to identify VCS for directory " ^ dir) 1734 | _ => ERROR ("Unable to identify VCS for directory " ^ dir)
1574 in 1735 in
1575 case vcs_maybe of 1736 case vcs_maybe of
1576 ERROR e => ERROR e 1737 ERROR e => ERROR e
1577 | OK vcs => 1738 | OK vcs =>
1578 case (fn HG => HgControl.id_of | GIT => GitControl.id_of) 1739 case (fn HG => HgControl.id_of
1740 | GIT => GitControl.id_of
1741 | SVN => SvnControl.id_of)
1579 vcs context "." of 1742 vcs context "." of
1580 ERROR e => ERROR ("Unable to obtain id of project repo: " 1743 ERROR e => ERROR ("Unable to find id of project repo: " ^ e)
1581 ^ e) 1744 | OK id =>
1582 | OK id => OK (vcs, id) 1745 case (fn HG => HgControl.copy_url_for
1746 | GIT => GitControl.copy_url_for
1747 | SVN => SvnControl.copy_url_for)
1748 vcs context "." of
1749 ERROR e => ERROR ("Unable to find URL of project repo: "
1750 ^ e)
1751 | OK url => OK (vcs, id, url)
1583 end 1752 end
1584 1753
1585 fun make_archive_root (context : context) = 1754 fun make_archive_root (context : context) =
1586 let val path = OS.Path.joinDirFile { 1755 let val path = OS.Path.joinDirFile {
1587 dir = #rootpath context, 1756 dir = #rootpath context,
1603 fun check_nonexistent path = 1772 fun check_nonexistent path =
1604 case SOME (OS.FileSys.fileSize path) handle OS.SysErr _ => NONE of 1773 case SOME (OS.FileSys.fileSize path) handle OS.SysErr _ => NONE of
1605 NONE => () 1774 NONE => ()
1606 | _ => raise Fail ("Path " ^ path ^ " exists, not overwriting") 1775 | _ => raise Fail ("Path " ^ path ^ " exists, not overwriting")
1607 1776
1608 fun file_url path = 1777 fun make_archive_copy target_name (vcs, project_id, source_url)
1609 let val forward_path =
1610 String.translate (fn #"\\" => "/" |
1611 c => Char.toString c) path
1612 in
1613 (* Path is expected to be absolute already, but if it
1614 starts with a drive letter, we'll need an extra slash *)
1615 case explode forward_path of
1616 #"/"::rest => "file:///" ^ implode rest
1617 | _ => "file:///" ^ forward_path
1618 end
1619
1620 fun make_archive_copy target_name (vcs, project_id)
1621 ({ context, ... } : project) = 1778 ({ context, ... } : project) =
1622 let val archive_root = make_archive_root context 1779 let val archive_root = make_archive_root context
1623 val synthetic_context = { 1780 val synthetic_context = {
1624 rootpath = archive_root, 1781 rootpath = archive_root,
1625 extdir = ".", 1782 extdir = ".",
1627 accounts = [] 1784 accounts = []
1628 } 1785 }
1629 val synthetic_library = { 1786 val synthetic_library = {
1630 libname = target_name, 1787 libname = target_name,
1631 vcs = vcs, 1788 vcs = vcs,
1632 source = URL_SOURCE (file_url (#rootpath context)), 1789 source = URL_SOURCE source_url,
1633 branch = DEFAULT_BRANCH, (* overridden by pinned id below *) 1790 branch = DEFAULT_BRANCH, (* overridden by pinned id below *)
1634 project_pin = PINNED project_id, 1791 project_pin = PINNED project_id,
1635 lock_pin = PINNED project_id 1792 lock_pin = PINNED project_id
1636 } 1793 }
1637 val path = archive_path archive_root target_name 1794 val path = archive_path archive_root target_name
1701 | TAR_BZ2 => "cjf" 1858 | TAR_BZ2 => "cjf"
1702 | TAR_XZ => "cJf", 1859 | TAR_XZ => "cJf",
1703 target_path, 1860 target_path,
1704 "--exclude=.hg", 1861 "--exclude=.hg",
1705 "--exclude=.git", 1862 "--exclude=.git",
1863 "--exclude=.svn",
1706 "--exclude=vext", 1864 "--exclude=vext",
1707 "--exclude=vext.sml", 1865 "--exclude=vext.sml",
1708 "--exclude=vext.ps1", 1866 "--exclude=vext.ps1",
1709 "--exclude=vext.bat", 1867 "--exclude=vext.bat",
1710 "--exclude=vext-project.json", 1868 "--exclude=vext-project.json",
1721 case packer_and_basename target_path of 1879 case packer_and_basename target_path of
1722 NONE => raise Fail ("Unsupported archive file extension in " 1880 NONE => raise Fail ("Unsupported archive file extension in "
1723 ^ target_path) 1881 ^ target_path)
1724 | SOME pn => pn 1882 | SOME pn => pn
1725 val details = 1883 val details =
1726 case project_vcs_and_id (#rootpath (#context project)) of 1884 case project_vcs_id_and_url (#rootpath (#context project)) of
1727 ERROR e => raise Fail e 1885 ERROR e => raise Fail e
1728 | OK details => details 1886 | OK details => details
1729 val archive_root = 1887 val archive_root =
1730 case make_archive_copy name details project of 1888 case make_archive_copy name details project of
1731 ERROR e => raise Fail e 1889 ERROR e => raise Fail e
1770 { 1928 {
1771 libname = libname, 1929 libname = libname,
1772 vcs = case vcs of 1930 vcs = case vcs of
1773 "hg" => HG 1931 "hg" => HG
1774 | "git" => GIT 1932 | "git" => GIT
1933 | "svn" => SVN
1775 | other => raise Fail ("Unknown version-control system \"" ^ 1934 | other => raise Fail ("Unknown version-control system \"" ^
1776 other ^ "\""), 1935 other ^ "\""),
1777 source = case (url, service, owner, repo) of 1936 source = case (url, service, owner, repo) of
1778 (SOME u, NONE, _, _) => URL_SOURCE u 1937 (SOME u, NONE, _, _) => URL_SOURCE u
1779 | (NONE, SOME ss, owner, repo) => 1938 | (NONE, SOME ss, owner, repo) =>
1781 | _ => raise Fail ("Must have exactly one of service " ^ 1940 | _ => raise Fail ("Must have exactly one of service " ^
1782 "or url string"), 1941 "or url string"),
1783 project_pin = project_pin, 1942 project_pin = project_pin,
1784 lock_pin = lock_pin, 1943 lock_pin = lock_pin,
1785 branch = case branch of 1944 branch = case branch of
1786 SOME b => BRANCH b 1945 NONE => DEFAULT_BRANCH
1787 | NONE => DEFAULT_BRANCH 1946 | SOME b =>
1947 case vcs of
1948 "svn" => raise Fail ("Branches not supported for " ^
1949 "svn repositories; change " ^
1950 "URL instead")
1951 | _ => BRANCH b
1788 } 1952 }
1789 end 1953 end
1790 1954
1791 fun load_userconfig () : userconfig = 1955 fun load_userconfig () : userconfig =
1792 let val home = FileBits.homedir () 1956 let val home = FileBits.homedir ()
1968 fun review_project ({ context, libs } : project) = 2132 fun review_project ({ context, libs } : project) =
1969 return_code_for (act_and_print (AnyLibControl.review context) 2133 return_code_for (act_and_print (AnyLibControl.review context)
1970 print_status_header (print_status true) 2134 print_status_header (print_status true)
1971 libs) 2135 libs)
1972 2136
1973 fun update_project ({ context, libs } : project) =
1974 let val outcomes = act_and_print
1975 (AnyLibControl.update context)
1976 print_outcome_header print_update_outcome libs
1977 val locks =
1978 List.concat
1979 (map (fn (libname, result) =>
1980 case result of
1981 ERROR _ => []
1982 | OK id => [{ libname = libname, id_or_tag = id }])
1983 outcomes)
1984 val return_code = return_code_for outcomes
1985 in
1986 if OS.Process.isSuccess return_code
1987 then save_lock_file (#rootpath context) locks
1988 else ();
1989 return_code
1990 end
1991
1992 fun lock_project ({ context, libs } : project) = 2137 fun lock_project ({ context, libs } : project) =
1993 let val outcomes = map (fn lib => 2138 let val _ = if FileBits.verbose ()
2139 then print ("Scanning IDs for lock file...\n")
2140 else ()
2141 val outcomes = map (fn lib =>
1994 (#libname lib, AnyLibControl.id_of context lib)) 2142 (#libname lib, AnyLibControl.id_of context lib))
1995 libs 2143 libs
1996 val locks = 2144 val locks =
1997 List.concat 2145 List.concat
1998 (map (fn (libname, result) => 2146 (map (fn (libname, result) =>
2005 in 2153 in
2006 if OS.Process.isSuccess return_code 2154 if OS.Process.isSuccess return_code
2007 then save_lock_file (#rootpath context) locks 2155 then save_lock_file (#rootpath context) locks
2008 else (); 2156 else ();
2009 return_code 2157 return_code
2158 end
2159
2160 fun update_project (project as { context, libs }) =
2161 let val outcomes = act_and_print
2162 (AnyLibControl.update context)
2163 print_outcome_header print_update_outcome libs
2164 val _ = if List.exists (fn (_, OK _) => true | _ => false) outcomes
2165 then lock_project project
2166 else OS.Process.success
2167 in
2168 return_code_for outcomes
2010 end 2169 end
2011 2170
2012 fun load_local_project pintype = 2171 fun load_local_project pintype =
2013 let val userconfig = load_userconfig () 2172 let val userconfig = load_userconfig ()
2014 val rootpath = OS.FileSys.getDir () 2173 val rootpath = OS.FileSys.getDir ()