pijul_org / pijul

feature: Improve the CLI of rollback

It is now possible to choose a patch name, a patch description and one or more authors for a ``rollback patch''. As a consquence, the CLI becomes more consistent with what tag and record propose.

By lthms on April 26, 2018
This patch is not signed.
8aVGZvKMCbZkLGWWPegvoTd6C3ezDp7B4o26m3Za8fSq9uxKWsfHC7eMe59yHRkJdQBjKuKsTqMeSDMMqzuJh1AJ
This patch is in the following branches:
latest
master
testing






















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22























23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39






40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use meta::{Global, Meta, load_global_or_local_signing_key};
use commands::tag::tag_args;
use commands::record::{decide_authors, decide_patch_message};
    tag_args(SubCommand::with_name("rollback")
             .arg(Arg::with_name("patch")
                  .help("Patch to roll back.")
                  .takes_value(true)
                  .multiple(true)
                  .validator(validate_base58)
             ))
    let global = Global::load().unwrap_or_else(|_| Global::new());
        let patch_date = args.value_of("date")
            .map_or(Ok(chrono::Utc::now()),
                    |x| chrono::DateTime::from_str(x)
                            .map_err(|_| ErrorKind::InvalidDate(String::from(x)))
        )?;
        let (name, description) = decide_patch_message(patch_name_arg,
                                                       patch_descr_arg,
                                                       !args.is_present("no-editor"),
                                                       &opts.repo_root,
                                                       &meta,
                                                       &global)?;
use clap::{Arg, ArgMatches, SubCommand};

use super::{ask, default_explain, validate_base58, BasicOptions, StaticSubcommand};
use meta::{load_signing_key, Global, Meta};
use std::collections::HashSet;
use std::path::Path;

use chrono;
use libpijul::fs_representation::{patch_file_name, RepoPath, RepoRoot};
use libpijul::patch::{Patch, PatchFlags};
use libpijul::{apply_resize, apply_resize_no_output, Hash, HashRef, PatchId};
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::iter;
use std::mem::drop;
use std::str::FromStr;

use commands::record::{decide_authors, decide_patch_message, record_args};
use error::Error;

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("rollback")
        .about("Create a patch with the inverse effects on the working copy of another patch.")
        .arg(Arg::with_name("repository")
             .long("repository")
             .help("Local repository.")
             .takes_value(true)
        )
        .arg(Arg::with_name("branch")
             .long("branch")
             .help("Branch.")
             .takes_value(true)
        )
        .arg(Arg::with_name("date")
             .long("date")
             .help("The date to use to create the patch, default is now.")
             .takes_value(true)
        )
        .arg(Arg::with_name("patch")
             .help("Patch to roll back.")
             .takes_value(true)
             .multiple(true)
             .validator(validate_base58)
        )
    record_args(
        SubCommand::with_name("rollback").arg(
            Arg::with_name("patch")
                .help("Patch to roll back.")
                .takes_value(true)
                .multiple(true)
                .validator(validate_base58),
        ),
    )
}

pub fn run(args: &ArgMatches) -> Result<(), Error> {
    let opts = BasicOptions::from_args(args)?;
    let patches: Option<HashSet<Hash>> = args
        .values_of("patch")
        .map(|ps| ps.map(|x| Hash::from_base58(x).unwrap()).collect());

    let patch_date = args.value_of("date")
        .map_or(Ok(chrono::Utc::now()),
                |x| chrono::DateTime::from_str(x)
                        .map_err(|_| ErrorKind::InvalidDate(String::from(x)))
    )?;

    let mut increase = 409600;
    let repo = opts.open_and_grow_repo(increase)?;
    let branch_name = opts.branch();

    let mut patches: HashMap<_, _> = if let Some(ref patches) = patches {
        let txn = repo.txn_begin()?;
        if let Some(branch) = txn.get_branch(&branch_name) {
            let mut patches_ = HashMap::new();
            for h in patches.iter() {
                debug!("unrecording {:?}", h);

                if let Some(internal) = txn.get_internal(h.as_ref()) {
                    if txn.get_patch(&branch.patches, internal).is_some() {
                        let patch = load_patch(&opts.repo_root, h.as_ref());
                        patches_.insert(h.to_owned(), patch);
                        continue;
                    }
                }
                return Err(Error::BranchDoesNotHavePatch {
                    branch_name: branch.name.as_str().to_string(),
                    patch: h.to_owned(),
                });
            }
            patches_
        } else {
            HashMap::new()
        }
    } else {
        let mut patches: Vec<_> = {
            let txn = repo.txn_begin()?;
            if let Some(branch) = txn.get_branch(&branch_name) {
                txn.rev_iter_applied(&branch, None)
                    .map(|(t, h)| {
                        let ext = txn.get_external(h).unwrap();
                        let patch = load_patch(&opts.repo_root, ext);
                        (ext.to_owned(), Some(h.to_owned()), patch, t)
                    })
                    .collect()
            } else {
                Vec::new()
            }
        };
        patches.sort_by(|&(_, _, _, a), &(_, _, _, b)| b.cmp(&a));
        let patches: Vec<(Hash, Option<PatchId>, Patch)> =
            patches.into_iter().map(|(a, b, c, _)| (a, b, c)).collect();
        // debug!("patches: {:?}", patches);
        let to_unrecord = ask::ask_patches(ask::Command::Unrecord, &patches).unwrap();
        debug!("to_unrecord: {:?}", to_unrecord);
        let patches: HashMap<_, _> = patches
            .into_iter()
            .filter(|&(ref k, _, _)| to_unrecord.contains(&k))
            .map(|(k, _, p)| (k, p))
            .collect();
        patches
    };

    let mut selected = Vec::new();
    loop {
        let hash = if let Some((hash, patch)) = patches.iter().next() {
            increase += patch.size_upper_bound() as u64;
            hash.to_owned()
        } else {
            break;
        };
        deps_dfs(&mut selected, &mut patches, &hash)
    }

    // Create the inverse changes.
    let mut changes = Vec::new();
    for &(ref hash, ref patch) in selected.iter() {
        debug!("inverting {:?}", patch);
        patch.inverse(hash, &mut changes)
    }

    let meta = Meta::load(&opts.repo_root).unwrap_or_else(|_| Meta::new());
    let (global, save_global) = Global::load()
        .map(|g| (g, false))
        .unwrap_or_else(|_| (Global::new(), true));

    if save_global {
        global.save().unwrap_or(())
    }

    // Create the inverse patch, and save it.
    let patch = {
        let authors_arg = args.values_of("author").map(|x| x.collect::<Vec<_>>());
        let patch_name_arg = args.value_of("message");
        let patch_descr_arg = args.value_of("description");

        let txn = repo.txn_begin()?;
        let authors = Vec::new();
        let name = String::new();
        let description = None;
        let authors = decide_authors(authors_arg, &meta, &global)?;

        let patch_date = args.value_of("date").map_or(Ok(chrono::Utc::now()), |x| {
            chrono::DateTime::from_str(x).map_err(|_| Error::InvalidDate {
                date: String::from(x),
            })
        })?;

        let (name, description) = decide_patch_message(
            patch_name_arg,
            patch_descr_arg,
            String::from(""),
            !args.is_present("no-editor"),
            &opts.repo_root,
            &meta,
            &global,
        )?;

        if let Some(branch) = txn.get_branch(&branch_name) {
8
9

10
11
    pijul add a
    pijul record -a -m "+a" -A "Me"
    echo yd | pijul rollback
    echo yd | pijul rollback -A "Me" -m "rollback"
}
16
17

18
19
    pijul_uncovered record -a -m "ac" -A "Me"

    echo yd | RUST_LOG="libpijul::apply=debug,libpijul::patch=debug,pijul::commands::rollback=debug" pijul rollback 2> /tmp/log
    echo yd | RUST_LOG="libpijul::apply=debug,libpijul::patch=debug,pijul::commands::rollback=debug" pijul rollback -A "me" -m "rollback" 2> /tmp/log
    pijul_uncovered revert -a
11
12



13
14
15
16
    HASH=$(pijul_uncovered record -a -m "+b" -A "Me" | sed -e 's/Recorded patch //')

    ! pijul rollback --branch blabla
    ! pijul rollback $(echo $HASH | sed -e "s/A/b/")
    pijul rollback $HASH
    ! pijul rollback --branch blabla  -A "me" -m "rollback"
    ! pijul rollback $(echo $HASH | sed -e "s/A/b/")  -A "me" -m "rollback"
    pijul rollback $HASH  -A "me" -m "rollback"
}
16
17

18
19
    pijul_uncovered record -a -m "ac" -A "Me"

    echo yd | pijul rollback
    echo yd | pijul rollback -A "Me" -m "ac"
    echo yd | RUST_LOG="libpijul::unrecord=debug" pijul unrecord 2> /tmp/log
15
16

17
18
    pijul_uncovered record -a -m " -file" -A "Me"

    echo yd | pijul rollback
    echo yd | pijul rollback  -A "me" -m "rollback"
    echo yd | pijul unrecord
17
18

19
20
    pijul_uncovered record -a -m " -file" -A "Me"

    echo yd | RUST_BACKTRACE=1 RUST_LOG="pijul=debug,libpijul::patch=debug" pijul rollback 2> /tmp/rblog
    echo yd | RUST_BACKTRACE=1 RUST_LOG="pijul=debug,libpijul::patch=debug" pijul rollback -A "me" -m "rollback" 2> /tmp/rblog
    echo yd | RUST_BACKTRACE=1 RUST_LOG="libpijul::unrecord=debug,libpijul::apply::find_alive" pijul unrecord 2> /tmp/log
24
25

26
27
    cd ../b
    pijul_uncovered pull -a ../a
    HASH2=$(pijul_uncovered rollback $HASH | sed -e "s/Recorded patch //")
    HASH2=$(pijul_uncovered rollback $HASH  -A "me" -m "rollback" | sed -e "s/Recorded patch //")