diff options
-rw-r--r-- | bdep/git.hxx | 16 | ||||
-rw-r--r-- | bdep/git.ixx | 20 | ||||
-rw-r--r-- | bdep/publish.cli | 2 | ||||
-rw-r--r-- | bdep/publish.cxx | 479 | ||||
-rw-r--r-- | bdep/utility.hxx | 1 | ||||
-rw-r--r-- | tests/publish.test | 512 |
6 files changed, 870 insertions, 160 deletions
diff --git a/bdep/git.hxx b/bdep/git.hxx index f56f38b..1552670 100644 --- a/bdep/git.hxx +++ b/bdep/git.hxx @@ -16,12 +16,26 @@ namespace bdep bool git (const dir_path&); + template <typename... A> + inline void + run_git (const dir_path& repo, A&&... args); + + template <typename I, typename O, typename E, typename... A> + inline process + start_git (I&& in, O&& out, E&& err, const dir_path& repo, A&&... args); + // Return the first line of the git output. If ignore_error is true, then - // suppress stderr, ignore (normal) error exist status, and return nullopt. + // suppress stderr, ignore (normal) error exit status, and return nullopt. // template <typename... A> optional<string> git_line (const dir_path& repo, bool ignore_error, A&&... args); + + // Similar to the above but takes the already started git process with a + // redirected output pipe. + // + optional<string> + git_line (process&& pr, fdpipe&& pipe, bool ignore_error = false); } #include <bdep/git.ixx> diff --git a/bdep/git.ixx b/bdep/git.ixx index 0ee94ac..e71375a 100644 --- a/bdep/git.ixx +++ b/bdep/git.ixx @@ -6,8 +6,24 @@ using namespace butl; namespace bdep { - optional<string> - git_line (process&&, fdpipe&&, bool); + template <typename... A> + inline void + run_git (const dir_path& repo, A&&... args) + { + return run ("git", "-C", repo, forward<A> (args)...); + } + + template <typename I, typename O, typename E, typename... A> + inline process + start_git (I&& in, O&& out, E&& err, const dir_path& repo, A&&... args) + { + return start (forward<I> (in), + forward<O> (out), + forward<E> (err), + "git", + "-C", repo, + forward<A> (args)...); + } template <typename... A> inline optional<string> diff --git a/bdep/publish.cli b/bdep/publish.cli index d8334aa..fce6a23 100644 --- a/bdep/publish.cli +++ b/bdep/publish.cli @@ -45,7 +45,7 @@ namespace bdep Along with the package archive, the submission request specifies the project the package belongs to, the repository section to publish the package under, the control repository URL to use for authorization, and - the publisher's email address for notifications. While the exact usage + the publisher's email address for notifications. While the exact usage and interpretation of this information depends on the specific repository, the following semantics apply when submitting to \cb{cppget.org}. diff --git a/bdep/publish.cxx b/bdep/publish.cxx index bea883e..c27609a 100644 --- a/bdep/publish.cxx +++ b/bdep/publish.cxx @@ -6,8 +6,12 @@ #include <cstdlib> // strtoul() +#include <libbutl/fdstream.mxx> // fdterm() #include <libbutl/manifest-parser.mxx> #include <libbutl/standard-version.mxx> +#include <libbutl/manifest-serializer.mxx> + +#include <libbpkg/manifest.hxx> #include <bdep/git.hxx> #include <bdep/project.hxx> @@ -297,7 +301,7 @@ namespace bdep v.push_back ("-s"); v.push_back ("-S"); // But show errors. } - else if (verb == 1) + else if (verb == 1 && fdterm (2)) v.push_back ("--progress-bar"); else if (verb > 3) v.push_back ("-v"); @@ -672,6 +676,8 @@ namespace bdep const dir_path& cfg, package_locations&& pkg_locs) { + using bpkg::package_manifest; + const url& repo (o.repository ()); optional<url> ctrl; @@ -692,7 +698,8 @@ namespace bdep fail << "unable to obtain publisher's email" << info << "use --email to specify explicitly"; - // Collect package information (version, project, section). + // Collect package information (version, project, section, archive + // path/checksum, and manifest). // // @@ It would have been nice to publish them in the dependency order. // Perhaps we need something like bpkg-pkg-order (also would be needed @@ -707,6 +714,8 @@ namespace bdep path archive; string checksum; + + package_manifest manifest; }; vector<package> pkgs; @@ -730,8 +739,13 @@ namespace bdep v.alpha () || v.major () == 0 ? "alpha" : v.beta () ? "beta" : "stable"); - pkgs.push_back ( - package {move (n), move (v), move (p), move (s), path (), string ()}); + pkgs.push_back (package {move (n), + move (v), + move (p), + move (s), + path () /* archive */, + string () /* checksum */, + package_manifest ()}); } // Print the plan and ask for confirmation. @@ -772,7 +786,8 @@ namespace bdep } // Prepare package archives and calculate their checksums. Also verify - // each archive with bpkg-pkg-verify for good measure. + // each archive with bpkg-pkg-verify and parse the package manifest it + // contains. // auto_rmdir dr_rm (tmp_dir ("publish")); const dir_path& dr (dr_rm.path); // dist.root @@ -802,9 +817,50 @@ namespace bdep if (!exists (c)) fail << "package distribution did not produce expected checksum " << c; - // Verify that archive name/content all match. + // Verify that archive name/content all match and while at it extract + // its manifest. // - run_bpkg (2 /* verbosity */, o, "pkg-verify", a); + process pr; + bool io (false); + try + { + fdpipe pipe (fdopen_pipe ()); // Text mode seems appropriate. + + pr = start_bpkg (2 /* verbosity */, + o, + pipe /* stdout */, + 2 /* stderr */, + "pkg-verify", + "--manifest", + a); + + pipe.out.close (); + ifdstream is (move (pipe.in), fdstream_mode::skip); + + manifest_parser mp (is, manifest_file.string ()); + p.manifest = package_manifest (mp); + is.close (); + } + // This exception is unlikely to be thrown as the package manifest is + // already validated by bpkg-pkg-verify. However, it's still possible if + // something is skew (e.g., different bpkg/bdep versions). + // + catch (const manifest_parsing& e) + { + finish_bpkg (o, pr); + + fail << "unable to parse package manifest in archive " << a << ": " + << e; + } + catch (const io_error&) + { + // Presumably the child process failed and issued diagnostics so let + // finish_bpkg() try to deal with that first. + // + io = true; + } + + finish_bpkg (o, pr, io); // Read the checksum. // @@ -825,6 +881,399 @@ namespace bdep p.archive = move (a); } + // Add the package archive "authorization" files to the build2-control + // branch. + // + // Their names are 16-character abbreviated checksums (a bit more than the + // standard 12 for security) and their content is the package manifest + // header (for the record). + // + // See if this is a VCS repository we recognize. + // + if (ctrl && git (prj)) + { + // Checkout the build2-control branch into a separate working tree not + // to interfere with the user's stuff. + // + auto_rmdir wd_rm (tmp_dir ("control")); + const dir_path& wd (wd_rm.path); + mk (wd); + + const dir_path submit_dir ("submit"); + dir_path sd (wd / submit_dir); + + // The 'git worktree add' command is quite verbose, printing something + // like: + // + // Preparing /tmp/hello/.bdep/tmp/control-14926-1 (identifier control-14926-1) + // HEAD is now at 3fd69a3 Publish hello/0.1.0-a.1 + // + // Note that there doesn't seem to be an option (yet) for suppressing + // this output. Also note that the first line is printed to stderr and + // the second one to stdout. So what we are going to do is redirect both + // stderr and stdout to /dev/null if the verbosity level is less than + // two and advise the user to re-run with -v on failure. + // + auto worktree_add = [&prj] (auto&&... args) + { + bool q (verb < 2); + auto_fd null (q ? fdnull () : auto_fd ()); + + process pr (start_git (0 /* stdin */, + q ? null.get () : 1 /* stdout */, + q ? null.get () : 2 /* stderr */, + prj, + "worktree", + "add", + forward<decltype(args)> (args)...)); + + if (pr.wait ()) + return; + + if (!q) + throw failed (); // Diagnostics already issued. + + assert (pr.exit); + + const process_exit& e (*pr.exit); + + if (e.normal ()) + fail << "unable to add worktree for build2-control branch" << + info << "re-run with -v for more information"; + else + fail << "git " << e; + }; + + auto worktree_prune = [&prj] () + { + run_git (prj, "worktree", "prune", verb > 2 ? "-v" : nullptr); + }; + + // Create the build2-control branch if it doesn't exist, from scratch if + // there is no remote-tracking branch and as a checkout with --track -b + // otherwise. If the local branch exists, then fast-forward it using the + // locally fetched data (no network). + // + // Note that we don't fetch in advance, so push conflicts are possible. + // The idea behind this is that it will be more efficient in most cases + // as the remote-tracking branch is likely to already be up-to-date due + // to the implicit branch fetches peformed by other operations like + // pull. In the rare conflict cases we will advise the user to run the + // fetch command and re-try. + // + bool local_exists (git_line (prj, + false /* ignore_error */, + "branch", + "--list", + "build2-control")); + + // @@ Should we allow using the remote name other than origin (here and + // everywhere) via the --remote option or smth? Maybe later. + // + bool remote_exists (git_line (prj, + false /* ignore_error */, + "branch", + "--list", + "--remote", + "origin/build2-control")); + + bool local_new (false); // The branch is created from scratch. + + // Note that the case where the local build2-control branch exists while + // the remote-tracking one doesn't is treated similar (but different) to + // the brand new branch: the upstream branch will be set up by the push + // operation but the local branch will not be deleted on the push + // failure. + // + if (!local_exists) + { + // Create the brand new local branch if the remote-tracking branch + // doesn't exist. + // + // The tricky part is to make sure that it doesn't inherit the current + // branch history. To accomplish this we will create an empty tree + // object, base the root (no parents) commit on it, and create the + // build2-control branch pointing to this commit. + // + if (!remote_exists) + { + // Create the empty tree object. + // + auto_fd null (fdnull ()); + fdpipe pipe (fdopen_pipe ()); + + process pr (start_git (null.get () /* stdin */, + pipe /* stdout */, + 2 /* stderr */, + prj, + "hash-object", + "-wt", "tree", + "--stdin")); + + optional<string> tree (git_line (move (pr), move (pipe))); + + if (!tree) + fail << "unable to create git tree object for build2-control"; + + // Create the (empty) root commit. + // + optional<string> commit (git_line (prj, + false, + "commit-tree", + "-m", "Start", + *tree)); + + if (!commit) + fail << "unable to create root git commit for build2-control"; + + // Create the branch. + // + // Note that we pass the empty oldvalue to make sure that the ref we + // are creating does not exist. It should be impossible but let's + // tighten things up a bit. + // + run_git (prj, + "update-ref", + "refs/heads/build2-control", + *commit, + "" /* oldvalue */); + + // Checkout the branch. Note that the upstream branch is not setup + // for it yet. This will be done by the push operation. + // + worktree_add (wd, "build2-control"); + + // Create the checksum files subdirectory. + // + mk (sd); + + local_new = true; + } + else + // Create the local branch, setting up the corresponding upstream + // branch. + // + worktree_add ("--track", + "-b", "build2-control", + wd, + "origin/build2-control"); + } + else + { + // Checkout the existing local branch. Note that we still need to + // fast-forward it (see below). + // + // Prune the build2-control worktree that could potentially stay from + // the interrupted previous publishing attempt. + // + worktree_prune (); + + worktree_add (wd, "build2-control"); + } + + // "Release" the checked out branch and delete the worktree, if exists. + // + // Note that until this is done the branch can not be checked out in any + // other worktree. + // + auto worktree_remove = [&prj, &wd, &wd_rm, &worktree_prune] () + { + if (exists (wd)) + { + // Note that we cannot (yet) use git-worktree-remove since it is not + // available in older versions. + // + rm_r (wd); + wd_rm.cancel (); + + worktree_prune (); + } + }; + + // Now, given that we successfully checked out the build2-control + // branch, add the authorization files for packages being published, + // commit, and push. Skip already existing files. Don't push if no files + // were added. + // + { + // Remove the control2-branch worktree on failure. Failed that we will + // get the 'build2-control is already checked out' error on the next + // publish attempt. + // + auto wg (make_exception_guard ([&worktree_remove] () + { + try + { + worktree_remove (); // Can throw failed. + } + catch (const failed&) + { + // We can't do much here and will let the user deal with the mess. + // Note that running 'git worktree prune' will likely be enough. + // Anyway, that's unlikely to happen. + } + })); + + // If the local branch existed from the beginning then fast-forward it + // over the remote-tracking branch. + // + // Note that fast-forwarding can potentially fail. That will mean the + // local branch has diverged from the remote one for some reason + // (e.g., inability to revert the commit, etc.). We again leave it to + // the use to deal with. + // + if (local_exists && remote_exists) + run_git (wd, + "merge", + verb < 2 ? "-q" : verb > 2 ? "-v" : nullptr, + "--ff-only", + "origin/build2-control"); + + // Create the authorization files and add them to the repository. + // + bool added (false); + + for (const package& p: pkgs) + { + // Use 16 characters of the sha256sum instead of 12 for extra + // security. + // + path ac (string (p.checksum, 0, 16)); + path mf (sd / ac); + + if (exists (mf)) + continue; + + try + { + ofdstream os (mf); + manifest_serializer s (os, mf.string ()); + p.manifest.serialize_header (s); + os.close (); + } + catch (const manifest_serialization&) + { + // This shouldn't happen as we just parsed the manifest. + // + assert (false); + } + catch (const io_error& e) + { + fail << "unable to write " << mf << ": " << e; + } + + run_git (wd, + "add", + verb > 2 ? "-v" : nullptr, + submit_dir / ac); + + added = true; + } + + // Commit and push. + // + // Note that we push even if we haven't committed anything in case we + // have added but haven't managed to push it on the previous run. + // + if (added) + { + // Format the commit message. + // + string m; + + auto pkg_str = [] (const package& p) + { + return p.name.string () + '/' + p.version.string (); + }; + + if (pkgs.size () == 1) + m = "Add " + pkg_str (pkgs[0]) + " publish authorization"; + else + { + m = "Add publish authorizations\n"; + + for (const package& p: pkgs) + { + m += '\n'; + m += pkg_str (p); + } + } + + run_git (wd, + "commit", + verb < 2 ? "-q" : verb > 2 ? "-v" : nullptr, + "-m", m); + } + + // If we fail to push the control branch, then revert the commit and + // advice the user to fetch the repository and re-try. + // + auto pg ( + make_exception_guard ( + [added, &prj, &wd, &worktree_remove, local_new] () + { + if (added) + try + { + // If the local build2-control branch was just created, then + // we need to drop not just the last commit but the whole + // branch (including it's root commit). Note that this is + // not an optimization. Imagine that the remote branch is + // not fetched yet and we just created the local one. If we + // leave this branch around after the failed push, then we + // will still be in trouble after the fetch since we won't + // be able to merge unrelated histories. + // + if (local_new) + { + worktree_remove (); // Release the branch before removal. + + run_git (prj, + "branch", + verb < 2 ? "-q" : nullptr, + "-D", + "build2-control"); + } + else + run_git (wd, + "reset", + verb < 2 ? "-q" : nullptr, + "--hard", + "HEAD^"); + + error << "unable to push build2-control branch" << + info << "run 'git fetch' and try again"; + } + catch (const failed&) + { + // We can't do much here and will leave the user to deal + // with the mess. Note that running 'git fetch' will not be + // enough as the local and remote branches are likely to + // have diverged. + } + })); + + if (verb) + text << "pushing build2-control"; + + // Note that we suppress the (too detailed) push command output if + // the verbosity level is 1. However, we still want to see the + // progress in this case. + // + run_git (wd, + "push", + + verb < 2 ? "-q" : verb > 3 ? "-v" : nullptr, + verb == 1 ? "--progress" : nullptr, + + !remote_exists + ? cstrings ({"--set-upstream", "origin", "build2-control"}) + : cstrings ()); + } + + worktree_remove (); + } + // Submit each package. // for (const package& p: pkgs) @@ -840,22 +1289,6 @@ namespace bdep if (verb) text << r.second << " (" << r.first << ")"; - - //@@ TODO [phase 2]: add checksum file to build2-control branch, commit - // and push (this will need some more discussion). - // - // - name (abbrev 12 char checksum) and subdir? - // - // - make the checksum file a manifest with basic info (name, version) - // - // - what if already exists (previous failed attempt)? Ignore? - // - // - make a separate checkout (in tmp facility) reusing the external - // .git/ dir? - // - // - should probably first fetch to avoid push conflicts. Or maybe - // fetch on push conflict (more efficient/robust)? - // } return 0; diff --git a/bdep/utility.hxx b/bdep/utility.hxx index a8a755b..57764fa 100644 --- a/bdep/utility.hxx +++ b/bdep/utility.hxx @@ -66,6 +66,7 @@ namespace bdep // <libbutl/fdstream.mxx> // + using butl::fdnull; using butl::fdopen_pipe; // Empty string and path. diff --git a/tests/publish.test b/tests/publish.test index 0b50270..f5ecc3a 100644 --- a/tests/publish.test +++ b/tests/publish.test @@ -15,202 +15,448 @@ repository = ($config.bdep.test.repository == [null] \ : 'https://cppget.org') \ : "$config.bdep.test.repository") -test.arguments += --repository "$repository" --control 'none' --yes \ ---email user@example.com +test.arguments += --repository "$repository" --yes --email user@example.com cxx = cc "config.cxx=$config.cxx" new += 2>! init += $cxx -d prj 2>! &prj/**/bootstrap/*** +windows = ($cxx.target.class == 'windows') + # Note that using the same package name and version for tests may result in # duplicate submissions. We will use unique version for each test, # incrementing the patch version for 1.0.X. # -# Next version to use: 1.0.8 +# Next version to use: 1.0.15 # -: single-pkg +: submit : { - test.arguments += --simulate 'success' + test.arguments += --control 'none' - : basic + : single-pkg : { - $clone_root_prj; - $init -C @cfg &prj-cfg/***; - sed -i -e 's/^(version:) .*$/\1 1.0.1/' prj/manifest; - - $* 2>>~%EOE% - synchronizing: - upgrade prj/1.0.1 - submitting prj-1.0.1.tar.gz - %. - %.*prj/1.0.1 submission is queued \(.{12}\)% - EOE + test.arguments += --simulate 'success' + + : basic + : + { + $clone_root_prj; + $init -C @cfg &prj-cfg/***; + sed -i -e 's/^(version:) .*$/\1 1.0.1/' prj/manifest; + + $* 2>>~%EOE% + synchronizing: + upgrade prj/1.0.1 + submitting prj-1.0.1.tar.gz + %.* + %prj/1\.0\.1 submission is queued \(.{12}\)% + EOE + } + + : no-cfg + : + { + $clone_root_prj; + + $* 2>>~%EOE% != 0 + %error: no default configuration in project .+% + info: use (@<cfg-name> | --config|-c <cfg-dir> | --all|-a) to specify configuration explicitly + EOE + } + + : multi-cfg + : + { + $clone_root_prj; + $init -C @cfg1 &prj-cfg1/***; + $init -C @cfg2 &prj-cfg2/***; + + $* --all 2>'error: multiple configurations specified for publish' != 0 + } } - : no-cfg + : multi-pkg : { - $clone_root_prj; - - $* 2>>~%EOE% != 0 - %error: no default configuration in project .+% - info: use (@<cfg-name> | --config|-c <cfg-dir> | --all|-a) to specify configuration explicitly - EOE + test.arguments += --simulate 'success' + + +$new -t empty prj &prj/*** + +$new --package -t lib libprj -d prj + +$new --package -t exe prj -d prj + + : both + : + { + $clone_prj; + sed -i -e 's/^(version:) .*$/\1 1.0.2/' prj/libprj/manifest; + sed -i -e 's/^(version:) .*$/\1 1.0.2/' prj/prj/manifest; + $init -C @cfg &prj-cfg/***; + + $* 2>>~%EOE% + submitting libprj-1.0.2.tar.gz + %.* + %libprj/1.0.2 submission is queued \(.{12}\)% + submitting prj-1.0.2.tar.gz + %.* + %prj/1\.0\.2 submission is queued \(.{12}\)% + EOE + } + + : single + : + { + $clone_prj; + sed -i -e 's/^(version:) .*$/\1 1.0.3/' prj/libprj/manifest; + $init -C @cfg &prj-cfg/***; + + # Publish the single libprj package rather than the whole prj project. + # + test.arguments = $regex.apply($test.arguments, '^(prj)$', '\1/libprj'); + + $* 2>>~%EOE% + submitting libprj-1.0.3.tar.gz + %.* + %libprj/1.0.3 submission is queued \(.{12}\)% + EOE + } + + : prompt + : + { + $clone_prj; + sed -i -e 's/^(version:) .*$/\1 1.0.4/' prj/libprj/manifest; + sed -i -e 's/^(version:) .*$/\1 1.0.4/' prj/prj/manifest; + $init -C @cfg &prj-cfg/***; + + # Suppress the --yes option. + # + test.arguments = $regex.apply($test.arguments, '^(--yes)$', ''); + + $* <'y' 2>>~"%EOE%" + publishing: + to: $repository + % as: .+@.+% + + package: libprj + version: 1.0.4 + project: prj + section: stable + + package: prj + version: 1.0.4 + project: prj + section: stable + continue? [y/n] submitting libprj-1.0.4.tar.gz + %.* + %libprj/1.0.4 submission is queued \\\(.{12}\\\)% + submitting prj-1.0.4.tar.gz + %.* + %prj/1\.0\.4 submission is queued \\\(.{12}\\\)% + EOE + } } - : multi-cfg + : failure : { - $clone_root_prj; - $init -C @cfg1 &prj-cfg1/***; - $init -C @cfg2 &prj-cfg2/***; - - $* --all 2>'error: multiple configurations specified for publish' != 0 + : duplicate-archive + : + { + test.arguments += --simulate 'duplicate-archive' + + $clone_root_prj; + $init -C @cfg &prj-cfg/***; + sed -i -e 's/^(version:) .*$/\1 1.0.5/' prj/manifest; + + $* 2>>~%EOE% != 0 + synchronizing: + upgrade prj/1.0.5 + submitting prj-1.0.5.tar.gz + %.* + error: duplicate submission + EOE + } + + : internal-error-text + : + { + test.arguments += --simulate 'internal-error-text' + + $clone_root_prj; + $init -C @cfg &prj-cfg/***; + sed -i -e 's/^(version:) .*$/\1 1.0.6/' prj/manifest; + + $* 2>>~%EOE% != 0 + synchronizing: + upgrade prj/1.0.6 + submitting prj-1.0.6.tar.gz + %.* + error: submission handling failed + % info: consider reporting this to .+ repository maintainers% + % info: checksum: .{64}% + EOE + } + + : internal-error-html + : + { + test.arguments += --simulate 'internal-error-html' + + $clone_root_prj; + $init -C @cfg &prj-cfg/***; + sed -i -e 's/^(version:) .*$/\1 1.0.7/' prj/manifest; + + $* 2>>~%EOE% != 0 + synchronizing: + upgrade prj/1.0.7 + submitting prj-1.0.7.tar.gz + %.* + error: HTTP status code 500 (internal server error) + % info: consider reporting this to .+ repository maintainers% + % info: checksum: .{64}% + EOE + } } } -: multi-pkg +: control : { - test.arguments += --simulate 'success' + # The control repository URL doesn't really matter for the submission + # simulation. We specify it to enable the control branch-related + # functionality. + # + test.arguments += --simulate 'success' --control "http://example.com/rep.git" + + # Create the remote repository. + # + +mkdir --no-cleanup prj.git + +git -C prj.git init --bar >! &prj.git/*** + + +$clone_prj - +$new -t empty prj &prj/*** - +$new --package -t lib libprj -d prj - +$new --package -t exe prj -d prj + clone_rep = cp --no-cleanup -r ../prj.git ./ &prj.git/*** + clone_prj = cp --no-cleanup -r ../prj ./ &prj/*** - : both + g = git -C prj >! 2>! + + : success : { + # Setup the remote repository. + # + rep = "file://($windows ? "$regex.replace($~, '\\', '/')" : "$~")/prj.git" + $clone_rep; + + # Setup the local repository/project. + # $clone_prj; - sed -i -e 's/^(version:) .*$/\1 1.0.2/' prj/libprj/manifest; - sed -i -e 's/^(version:) .*$/\1 1.0.2/' prj/prj/manifest; $init -C @cfg &prj-cfg/***; + $g remote add origin "$rep"; - $* 2>>~%EOE% - submitting libprj-1.0.2.tar.gz - %. - %.*libprj/1.0.2 submission is queued \(.{12}\)% - submitting prj-1.0.2.tar.gz - %. - %.*prj/1.0.2 submission is queued \(.{12}\)% + # Publish when neither local nor remote-tracking build2-control branches + # are present. + # + sed -i -e 's/^(version:) .*$/\1 1.0.8/' prj/manifest; + + $* >&2 2>>~%EOE%; + synchronizing: + upgrade prj/1.0.8 + pushing build2-control + %.* + Branch 'build2-control' set up to track remote branch 'build2-control' from 'origin'. + submitting prj-1.0.8.tar.gz + %.* + %prj/1\.0\.8 submission is queued \(.{12}\)% EOE - } - : single - : - { - $clone_prj; - sed -i -e 's/^(version:) .*$/\1 1.0.3/' prj/libprj/manifest; - $init -C @cfg &prj-cfg/***; + # Publish when both local and remote-tracking build2-control branches are + # present. + # + sed -i -e 's/^(version:) .*$/\1 1.0.9/' prj/manifest; - # Publish the single libprj package rather than the whole prj project. + $* 2>>~%EOE%; + synchronizing: + upgrade prj/1.0.9 + pushing build2-control + %.* + submitting prj-1.0.9.tar.gz + %.* + %prj/1\.0\.9 submission is queued \(.{12}\)% + EOE + + # Publish when the local build2-control branch is not present while the + # remote-tracking branch is. # - test.arguments = $regex.apply($test.arguments, '^(prj)$', '\1/libprj'); + sed -i -e 's/^(version:) .*$/\1 1.0.10/' prj/manifest; - $* 2>>~%EOE% - submitting libprj-1.0.3.tar.gz - %. - %.*libprj/1.0.3 submission is queued \(.{12}\)% + $g branch -D build2-control; + + $* 2>>~%EOE%; + synchronizing: + upgrade prj/1.0.10 + pushing build2-control + %.* + submitting prj-1.0.10.tar.gz + %.* + %prj/1\.0\.10 submission is queued \(.{12}\)% EOE + + # Check for the build2-control branch commits presence and the files they + # add. + # + $g checkout build2-control; + $g log --name-only --pretty='format:%s' >>:~%EOO% + Add prj/1.0.10 publish authorization + %submit/.{16}% + + Add prj/1.0.9 publish authorization + %submit/.{16}% + + Add prj/1.0.8 publish authorization + %submit/.{16}% + + Start + EOO } - : prompt + : failure : { + g2 = git -C prj2 >! 2>! + + # Setup the remote repository. + # + rep = "file://($windows ? "$regex.replace($~, '\\', '/')" : "$~")/prj.git" + $clone_rep; + + # Setup the local repository/project. + # $clone_prj; - sed -i -e 's/^(version:) .*$/\1 1.0.4/' prj/libprj/manifest; - sed -i -e 's/^(version:) .*$/\1 1.0.4/' prj/prj/manifest; $init -C @cfg &prj-cfg/***; + $g remote add origin "$rep"; - # Suppress the --yes option. - # - test.arguments = $regex.apply($test.arguments, '^(--yes)$', ''); - - $* <'y' 2>>~"%EOE%" - publishing: - to: $repository - % as: .+@.+% - - package: libprj - version: 1.0.4 - project: prj - section: stable - - package: prj - version: 1.0.4 - project: prj - section: stable - continue? [y/n] submitting libprj-1.0.4.tar.gz - %. - %.*libprj/1.0.4 submission is queued \\\(.{12}\\\)% - submitting prj-1.0.4.tar.gz - %. - %.*prj/1.0.4 submission is queued \\\(.{12}\\\)% - EOE - } -} + # Publish successfully. + # + sed -i -e 's/^(version:) .*$/\1 1.0.11/' prj/manifest; -: failure -: -{ - : duplicate-archive - : - { - test.arguments += --simulate 'duplicate-archive' + $* >! 2>!; - $clone_root_prj; - $init -C @cfg &prj-cfg/***; - sed -i -e 's/^(version:) .*$/\1 1.0.5/' prj/manifest; + # Push into the build2-control branch from another local repository, + # making the subsequent publish of prj package impossible until the next + # fetch. + # + git clone "$rep" prj2 &prj2/*** 2>!; + $g2 checkout -b build2-control --track origin/build2-control; + $g2 commit --allow-empty -m 'Dummy1'; + $g2 push; - $* 2>>~%EOE% != 0 + sed -i -e 's/^(version:) .*$/\1 1.0.12/' prj/manifest; + + # Fail to publish (see above for details). + # + $* 2>>~%EOE% != 0; synchronizing: - upgrade prj/1.0.5 - submitting prj-1.0.5.tar.gz - %. - %.*error: duplicate submission% + upgrade prj/1.0.12 + pushing build2-control + %.* + error: unable to push build2-control branch + info: run 'git fetch' and try again EOE - } - : internal-error-text - : - { - test.arguments += --simulate 'internal-error-text' + # Publish successfully after the fetch. + # + $g fetch; + + $* 2>>~%EOE%; + pushing build2-control + %.* + submitting prj-1.0.12.tar.gz + %.* + %prj/1\.0\.12 submission is queued \(.{12}\)% + EOE - $clone_root_prj; - $init -C @cfg &prj-cfg/***; - sed -i -e 's/^(version:) .*$/\1 1.0.6/' prj/manifest; + sed -i -e 's/^(version:) .*$/\1 1.0.13/' prj/manifest; - $* 2>>~%EOE% != 0 + # Reproduce the situation when the local build2-control branch exists but + # the remote-tracking one doesn't (see brep/publish.cxx for details). + # + $g branch -D -r origin/build2-control; + + $* >&2 2>>~%EOE%; synchronizing: - upgrade prj/1.0.6 - submitting prj-1.0.6.tar.gz - %. - %.*error: submission handling failed% - % info: consider reporting this to .+ repository maintainers% - % info: checksum: .{64}% + upgrade prj/1.0.13 + pushing build2-control + %.* + Branch 'build2-control' set up to track remote branch 'build2-control' from 'origin'. + submitting prj-1.0.13.tar.gz + %.* + %prj/1\.0\.13 submission is queued \(.{12}\)% EOE - } - : internal-error-html - : - { - test.arguments += --simulate 'internal-error-html' + # Test publishing after implicit fetches. + # + # Push again into the build2-control branch from another local repository + # (see above for details). + # + $g2 pull; + $g2 commit --allow-empty -m 'Dummy2'; + $g2 push; - $clone_root_prj; - $init -C @cfg &prj-cfg/***; - sed -i -e 's/^(version:) .*$/\1 1.0.7/' prj/manifest; + sed -i -e 's/^(version:) .*$/\1 1.0.14/' prj/manifest; + + # Note that the prj repository master branch push doesn't implicitly fetch + # the build2-control branch, so the subsequent publishing fails. + # + $g add '*'; + $g commit -m 'Create'; + $g push --set-upstream origin master; - $* 2>>~%EOE% != 0 + $* 2>>~%EOE% != 0; synchronizing: - upgrade prj/1.0.7 - submitting prj-1.0.7.tar.gz - %. - %.*error: HTTP status code 500 \(internal server error\)% - % info: consider reporting this to .+ repository maintainers% - % info: checksum: .{64}% + upgrade prj/1.0.14 + pushing build2-control + %.* + error: unable to push build2-control branch + info: run 'git fetch' and try again EOE + + # Note that the prj repository master branch pull fetches the + # build2-control branch implicitly, so the subsequent publishing succeeds. + # + $g pull; + + $* 2>>~%EOE%; + pushing build2-control + %.* + submitting prj-1.0.14.tar.gz + %.* + %prj/1\.0\.14 submission is queued \(.{12}\)% + EOE + + # Check the build2-control branch commits presence and the files they add. + # + $g checkout build2-control; + $g log --name-only --pretty='format:%s' >>:~%EOO% + %.* + Add prj/1.0.14 publish authorization + %submit/.{16}% + + Dummy2 + Add prj/1.0.13 publish authorization + %submit/.{16}% + + Add prj/1.0.12 publish authorization + %submit/.{16}% + + Dummy1 + Add prj/1.0.11 publish authorization + %submit/.{16}% + + Start + EOO } } |