pijul_org / pijul

Add support for showing patches with pijul log

By eh on April 24, 2019
This patch is not signed.
7vhPfUMQtoGFzR6UnmEiAunJcdzzZN97Cfw1y2PDaon2pvJG5fXiP7s1zriNXNjx6WpnozT1WumWckyYkdXjjje1
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
use libpijul::{HashRef, PatchId, Repository};
    repo: &Repository,
    branch_name: &String,
        if settings.show_patches {
            let mut patch_path = settings.opts.repo_root.patches_dir().join(hash_ext.to_base58());
            patch_path.set_extension("gz");
            let f = File::open(&patch_path)?;

            let mut f = BufReader::new(f);
            let (hash, _, patch) = Patch::from_reader_compressed(&mut f)?;

            print_patch(&hash, &patch, &repo, branch_name)?;
            println!();
        }

    let branch_name = &settings.opts.branch();
    let branch = match txn.get_branch(branch_name) {
            display_patch(&settings, n, patchid, hash_ext, &repo, branch_name)?;
                    display_patch(&settings, n, patchid, hash_ext, &repo, branch_name)?;
            display_patch(&settings, n, patchid, hash_ext, &repo, branch_name)?;
use clap::{Arg, ArgMatches, SubCommand};
use commands::patch::print_patch;
use commands::{ask, default_explain, BasicOptions, StaticSubcommand};
use error::Error;
use libpijul::fs_representation::RepoPath;
use libpijul::patch::Patch;
use libpijul::{Branch, PatchId, Txn};
use regex::Regex;
use std::fs::File;
use std::io::Read;
use std::io::Read;
use std::io::{BufReader, Read};
use std::path::PathBuf;
use term;

pub fn invocation() -> StaticSubcommand {
    SubCommand::with_name("log")
        .about("List the patches applied to the given branch")
        .arg(
            Arg::with_name("repository")
                .long("repository")
                .help("Path to the repository to list.")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("branch")
                .long("branch")
                .help("The branch to list.")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("internal-id")
                .long("internal-id")
                .help("Display only patches with these internal identifiers.")
                .multiple(true)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("hash-only")
                .long("hash-only")
                .help("Only display the hash of each path."),
        )
        .arg(
            Arg::with_name("repository-id")
                .long("repository-id")
                .help("display a header with the repository id")
        )
        .arg(
            Arg::with_name("path")
                .long("path")
                .multiple(true)
                .takes_value(true)
                .help("Only display patches that touch the given path."),
        )
        .arg(
            Arg::with_name("grep")
                .long("grep")
                .multiple(true)
                .takes_value(true)
                .help("Search patch name and description with a regular expression."),
        )
        .arg(
            Arg::with_name("last")
                .long("last")
                .takes_value(true)
                .help("Show only the last n patches. If `--first m` is also used, then (a) if the command normally outputs the last patches first, this means the last n patches of the first m ones. (b) Else, it means the first m patches of the last n ones."),
        )
        .arg(
            Arg::with_name("first")
                .long("first")
                .takes_value(true)
                .help("Show only the last n patches. If `--last m` is also used, then (a) if the command normally outputs the last patches first, this means the last m patches of the first n ones. (b) Else, it means the first n patches of the last m ones."),
        )
        .arg(
            Arg::with_name("patch")
                .long("patch")
                .short("p")
                .help("Show patches"),
        )
}

struct Settings<'a> {
    hash_only: bool,
    show_repoid: bool,
    show_patches: bool,
    regex: Vec<Regex>,
    opts: BasicOptions<'a>,
    path: Vec<RepoPath<PathBuf>>,
    first: Option<usize>,
    last: Option<usize>,
}

impl<'a> Settings<'a> {
    fn parse(args: &'a ArgMatches) -> Result<Self, Error> {
        let basic_opts = BasicOptions::from_args(args)?;
        let hash_only = args.is_present("hash-only");
        let first = args.value_of("first").and_then(|x| x.parse().ok());
        let last = args.value_of("last").and_then(|x| x.parse().ok());
        let show_patches = args.is_present("patch");
        let show_repoid = args.is_present("repository-id");
        let mut regex = Vec::new();
        if let Some(regex_args) = args.values_of("grep") {
            for r in regex_args {
                debug!("regex: {:?}", r);
                regex.push(Regex::new(r)?)
            }
        }
        let path = match args.values_of("path") {
            Some(arg_paths) => {
                let mut paths = Vec::new();
                for path in arg_paths {
                    let p = basic_opts.cwd.join(path);
                    let p = if let Ok(p) = std::fs::canonicalize(&p) {
                        p
                    } else {
                        p
                    };
                    paths.push(basic_opts.repo_root.relativize(&p)?.to_owned());
                }
                paths
            }
            None => Vec::new(),
        };
        Ok(Settings {
            hash_only,
            show_patches,
            show_repoid,
            regex,
            opts: basic_opts,
            path,
            first,
            last,
        })
    }
}

impl<'a> Settings<'a> {
    fn display_patch_(
        &self,
        txn: &Txn,
        branch: &Branch,
        nth: u64,
        patchid: PatchId,
    ) -> Result<(), Error> {
        let hash_ext = txn.get_external(patchid).unwrap();
        debug!("hash: {:?}", hash_ext.to_base58());

        let (matches_regex, o_patch) = if self.regex.is_empty() {
            (true, None)
        } else {
            let patch = self.opts.repo_root.read_patch_nochanges(hash_ext)?;
            let does_match = {
                let descr = match patch.description {
                    Some(ref d) => d,
                    None => "",
                };
                self.regex
                    .iter()
                    .any(|ref r| r.is_match(&patch.name) || r.is_match(descr))
            };
            (does_match, Some(patch))
        };
        if !matches_regex {
            return Ok(());
        };

        if self.hash_only {
            println!("{}:{}", hash_ext.to_base58(), nth);
        } else {
            let patch = match o_patch {
                None => self.opts.repo_root.read_patch_nochanges(hash_ext)?,
                Some(patch) => patch,
            };
            let mut term = term::stdout();
            ask::print_patch_descr(&mut term, &hash_ext.to_owned(), Some(patchid), &patch);
        }

        if self.show_patches {
            let mut patch_path = self.opts.repo_root.patches_dir().join(hash_ext.to_base58());
            patch_path.set_extension("gz");
            let f = File::open(&patch_path)?;

            let mut f = BufReader::new(f);
            let (hash, _, patch) = Patch::from_reader_compressed(&mut f)?;

            print_patch(&hash, &patch, txn, branch)?;
            println!();
        }

        Ok(())
    }

    fn display_patch(
        &self,
        txn: &Txn,
        branch: &Branch,
        n: u64,
        patchid: PatchId,
    ) -> Result<(), Error> {
        if self.path.is_empty() {
            self.display_patch_(txn, branch, n, patchid)?;
        } else {
            for path in self.path.iter() {
                let inode = txn.find_inode(&path)?;
                let key = if let Some(key) = txn.get_inodes(inode) {
                    key.key
                } else {
                    continue;
                };
                if txn.get_touched(key, patchid) {
                    self.display_patch_(txn, branch, n, patchid)?;
                    break;
                }
            }
        }
        Ok(())
    }

    fn is_touched(&self, txn: &Txn, patchid: PatchId) -> bool {
        self.path.is_empty()
            || self.path.iter().any(|path| {
                if let Ok(inode) = txn.find_inode(&path) {
                    if let Some(key) = txn.get_inodes(inode) {
                        return txn.get_touched(key.key, patchid);
                    }
                }
                false
            })
    }
}

pub fn run(args: &ArgMatches) -> Result<(), Error> {
    let settings = Settings::parse(args)?;
    let repo = settings.opts.open_repo()?;
    let txn = repo.txn_begin()?;
    let branch = match txn.get_branch(&settings.opts.branch()) {
        Some(b) => b,
        None => return Err(Error::NoSuchBranch),
    };

    if settings.show_repoid {
        let id_file = settings.opts.repo_root.id_file();
        let mut f = File::open(&id_file)?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        if settings.hash_only {
            println!("{}", s.trim());
        } else {
            println!("Repository id: {}", s.trim());
            println!();
        }
    };
    if settings.hash_only {
        // If in binary form, show the patches in chronological order.
        let start = settings.last.and_then(|last| {
            txn.rev_iter_applied(&branch, None)
                .filter(|(_, patchid)| {
                    // Only select patches that touch the input path
                    // (if that path exists).
                    settings.is_touched(&txn, *patchid)
                })
                .take(last)
                .last()
                .map(|(n, _)| n)
        });
        debug!("start {:?}", start);
        for (n, (applied, patchid)) in txn.iter_applied(&branch, start).enumerate() {
            if let Some(first) = settings.first {
                if n >= first {
                    break;
                }
            }
            settings.display_patch(&txn, &branch, applied, patchid)?
        }
        return Ok(());
    }

    let txn = repo.txn_begin()?;
    if let Some(v) = args.values_of("internal-id") {
        for (n, patchid) in v.filter_map(|x| PatchId::from_base58(x)).enumerate() {
            settings.display_patch(&txn, &branch, n as u64, patchid)?;
        }
    } else {
        let first = if let Some(first) = settings.first {
            txn.iter_applied(&branch, None)
                .filter(|(_, patchid)| settings.is_touched(&txn, *patchid))
                .take(first)
                .last()
                .map(|(n, _)| n)
        } else {
            None
        };
        for (n, (applied, patchid)) in txn.rev_iter_applied(&branch, first).enumerate() {
            if let Some(last) = settings.last {
                if n >= last {
                    break;










1
2
3
4
5
6
7
8

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115








116
117
118
119
120
121
122
123
124
125






126
127
128
129
130
131



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156








































157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use libpijul::{Branch, EdgeFlags, Hash, Key, LineId, PatchId, Repository, Transaction, Txn, Value, ROOT_KEY};
                print_patch(&hash, &patch, &repo, &branch_name)?
pub fn print_patch(
    hash: &Hash,
    patch: &Patch,
    repo: &Repository,
    branch_name: &String,
) -> Result<(), Error> {
    let txn = repo.txn_begin()?;
    let branch = txn.get_branch(branch_name).expect("Branch not found");
use super::validate_base58;
use atty;
use clap::{Arg, ArgGroup, ArgMatches, SubCommand};
use commands::{default_explain, BasicOptions, StaticSubcommand};
use error::Error;
use libpijul;
use libpijul::graph::LineBuffer;
use libpijul::patch::{Change, NewEdge, Patch};
use libpijul::{Branch, EdgeFlags, Hash, Key, LineId, PatchId, Transaction, Txn, Value, ROOT_KEY};
use libpijul::{Branch, EdgeFlags, Hash, Key, LineId, PatchId, Transaction, Txn, Value, ROOT_KEY};
use std::cmp::max;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{copy, stdout, BufReader};
use std::str::from_utf8;
use term;
use term::StdoutTerminal;

pub fn invocation() -> StaticSubcommand {
    SubCommand::with_name("patch")
        .about("Output a patch")
        .arg(
            Arg::with_name("repository")
                .long("repository")
                .help("Path to the repository where the patches will be applied.")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("patch")
                .help("The hash of the patch to be printed.")
                .takes_value(true)
                .required(true)
                .validator(validate_base58),
        )
        .arg(
            Arg::with_name("bin")
                .long("bin")
                .help("Output the patch in binary."),
        )
        .arg(
            Arg::with_name("name")
                .long("name")
                .help("Output the patch name."),
        )
        .arg(
            Arg::with_name("description")
                .long("description")
                .help("Output the patch description."),
        )
        .arg(
            Arg::with_name("authors")
                .long("authors")
                .help("Output the patch authors."),
        )
        .arg(
            Arg::with_name("date")
                .long("date")
                .help("Output the patch date."),
        )
        .group(ArgGroup::with_name("details").required(false).args(&[
            "bin",
            "name",
            "description",
            "date",
            "authors",
        ]))
}

#[derive(PartialEq, Eq)]
enum View {
    Normal,
    Bin,
    NameOnly,
    DescrOnly,
    DateOnly,
    AuthorsOnly,
}

pub fn run(args: &ArgMatches) -> Result<(), Error> {
    let opts = BasicOptions::from_args(args)?;
    let patch = Hash::from_base58(args.value_of("patch").unwrap()).unwrap();
    let mut patch_path = opts.repo_root.patches_dir().join(&patch.to_base58());
    patch_path.set_extension("gz");
    let mut f = File::open(&patch_path)?;

    let v: View = match (
        args.is_present("bin"),
        args.is_present("name"),
        args.is_present("description"),
        args.is_present("date"),
        args.is_present("authors"),
    ) {
        (true, _, _, _, _) => View::Bin,
        (_, true, _, _, _) => View::NameOnly,
        (_, _, true, _, _) => View::DescrOnly,
        (_, _, _, true, _) => View::DateOnly,
        (_, _, _, _, true) => View::AuthorsOnly,
        (_, _, _, _, _) => View::Normal,
    };

    if v == View::Bin {
        let mut stdout = stdout();
        copy(&mut f, &mut stdout)?;
    } else {
        // Write the patch in text.
        let mut f = BufReader::new(f);
        let (hash, _, patch) = Patch::from_reader_compressed(&mut f)?;

        match v {
            View::AuthorsOnly => print!("{:?}", patch.authors),
            View::DescrOnly => print!("{}", patch.description.clone().unwrap_or("".into())),
            View::DateOnly => print!("{:?}", patch.timestamp),
            View::NameOnly => print!("{}", patch.name),
            _ => {
                // it cannot be View::Bin, so it has to be View::Normal
                let repo = opts.open_repo()?;
                let txn = repo.txn_begin()?;
                let branch = txn.get_branch(&opts.branch()).expect("Branch not found");
                let internal = txn
                    .get_internal(hash.as_ref())
                    .expect("Patch not in repository")
                    .expect("Patch not in repository")
                    .expect("Patch not in repository")
                    .to_owned();
                let branch_name = opts.branch();
                let txn = repo.txn_begin()?;
                let branch = txn.get_branch(&branch_name).expect("Branch not found");
                print_patch(&hash, &patch, &txn, &branch)?
            }
        }
    }
    Ok(())
}

                let mut buf = LineNumbers {
                    n: 0, // The graph will start from a file base name.
                    patch: internal.clone(),
                    current_file: ROOT_KEY,
                    numbers: HashMap::new(),
                };
pub fn print_patch(hash: &Hash, patch: &Patch, txn: &Txn, branch: &Branch) -> Result<(), Error> {
    let internal = txn
        .get_internal(hash.as_ref())
        .expect("Patch not in repository")
        .to_owned();

                let mut terminal = if atty::is(atty::Stream::Stdout) {
                    term::stdout()
                    term::stdout()
    let mut buf = LineNumbers {
        n: 0, // The graph will start from a file base name.
        patch: internal.clone(),
        current_file: ROOT_KEY,
        numbers: HashMap::new(),
    };

    let mut terminal = if atty::is(atty::Stream::Stdout) {
        term::stdout()
    } else {
        None
    };
    for c in patch.changes() {
        match *c {
            Change::NewNodes {
                ref up_context,
                ref flag,
                ref nodes,
                ref line_num,
                ..
            } => {
                if flag.contains(EdgeFlags::FOLDER_EDGE) {
                    /*render_new_folder(&txn, branch, deleted_files,
                    internal, up_context, down_context, nodes)?*/
                } else {
                    None
                };
                for c in patch.changes() {
                    match *c {
                        Change::NewNodes {
                            ref up_context,
                            ref flag,
                            ref nodes,
                            ref line_num,
                            ..
                        } => {
                            if flag.contains(EdgeFlags::FOLDER_EDGE) {
                                /*render_new_folder(&txn, branch, deleted_files,
                                internal, up_context, down_context, nodes)?*/
                            } else {
                                render_new_change(
                                    &mut terminal,
                                    &txn,
                                    &branch,
                                    &mut buf,
                                    internal,
                                    up_context,
                                    line_num,
                                    nodes,
                                )?
                            }
                        }
                        Change::NewEdges {
                            ref edges, flag, ..
                        } => render_new_edges(
                            &mut terminal,
                            &txn,
                            &branch,
                            &mut buf,
                            internal,
                            edges,
                            flag,
                        )?,
                    }
                    }
                    render_new_change(
                        &mut terminal,
                        &txn,
                        &branch,
                        &mut buf,
                        internal,
                        up_context,
                        line_num,
                        nodes,
                    )?
                }
            }
            Change::NewEdges {
                ref edges, flag, ..
            } => render_new_edges(
                &mut terminal,
                &txn,
                &branch,
                &mut buf,
                internal,
                edges,
                flag,
            )?,
        }