pijul_org / pijul

refactor: Move get_current_branch into libpijul

By lthms on May 11, 2018
This patch is not signed.
8z77ci4wMbRnzwRDhEGrhjKs9MaLcjgsasJQreoFFa3yQjCXVR8zSk8ZQvy3wQXqEhzBtw1xCThfsj24MxKKxFwy
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355

357
358
359


362




366


369


372


375
use backend::{Hash, HashRef, MutTxn, ROOT_INODE};
use rand::Rng;
use rand;
use std::collections::HashSet;
use std::fs::{metadata, create_dir_all, File};
use std::io::{Write, BufReader, Read};
use std;
}

pub fn get_current_branch(root: &Path) -> Result<String> {
    debug!("path: {:?}", root);
    let mut path = repo_dir(&canonicalize(root)?);
    path.push("current_branch");
    if let Ok(mut f) = File::open(&path) {
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s.trim().to_string())
    } else {
        Ok(DEFAULT_BRANCH.to_string())
    }
}

pub fn set_current_branch(root: &Path, branch: &str) -> Result<()> {
    debug!("set current branch: root={:?}, branch={:?}", root, branch);
    let mut path = repo_dir(&canonicalize(root)?);
    path.push("current_branch");
    let mut f = File::create(&path)?;
    f.write_all(branch.trim().as_ref())?;
    f.write_all(b"\n")?;
//! Layout of a repository (files in `.pijul`) on the disk. This
//! module exports both high-level functions that require no knowledge
//! of the repository, and lower-level constants documented on
//! [pijul.org/documentation/repository](https://pijul.org/documentation/repository),
//! used for instance for downloading files from remote repositories.

use std::path::{Path, PathBuf};
use std::fs::{metadata, create_dir_all, File};
use std::io::{Write, BufReader};
use std::collections::HashSet;
use bs58;
use bs58;
use std;
use backend::{Hash, HashRef, MutTxn, ROOT_INODE};
use patch::{Patch, PatchHeader};
use super::Repository;
use backend::DEFAULT_BRANCH;
use backend::{Hash, HashRef};
use backend::{MutTxn, ROOT_INODE};
use bs58;
use flate2;
use rand;
use rand::Rng;
use ignore::overrides::OverrideBuilder;
use ignore::WalkBuilder;
use patch::{Patch, PatchHeader};
use rand::distributions::Alphanumeric;
use rand::Rng;
use std;
use std::fs::canonicalize;
use std::fs::{create_dir_all, metadata, File};
use std::io::{BufReader, Read, Write};
use std::path::{Path, PathBuf};
use {Result, Error};
use std::ffi::OsStr;

/// Given a Path-like type P, RepoPath<P> is a 'P' relative to some fs_representation::RepoRoot
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct RepoPath<P: ?Sized>(pub P);

pub fn in_repo_root() -> RepoPath<&'static Path> {
    RepoPath(Path::new(""))
}

impl RepoPath<std::path::PathBuf> {
    pub fn push(&mut self, x: &str) {
        self.0.push(x)
    }

    pub fn pop(&mut self) -> bool {
        self.0.pop()
    }

    pub fn new() -> Self {
        RepoPath(PathBuf::new())
    }
    
    pub fn as_ref(&self) -> RepoPath<&Path> {
        RepoPath(self.0.as_ref())
    }

    pub fn set_file_name(&mut self, filename: &OsStr) {
        self.0.set_file_name(filename)
    }

    pub fn from_string(path: String) -> Self {
        RepoPath(PathBuf::from(path))
    }
}

    
impl<P: AsRef<Path>> RepoPath<P> {
    pub fn as_path(&self) -> &std::path::Path {
        self.0.as_ref()
    }
}

impl<P: AsRef<Path>> RepoPath<P> {    
    pub fn parent(&self) -> Option<RepoPath<&std::path::Path>> {
        self.as_path().parent().map(RepoPath)
    }

    pub fn file_name(&self) -> Option<&OsStr> {
        self.as_path().file_name()
    }

    pub fn split(&self) -> Option<(RepoPath<&std::path::Path>, &OsStr)> {
        self.parent().map(|p| {(p, self.file_name().expect("file_name and parent should be consistent"))})
    }

    pub fn components(&self) -> std::path::Components {
        self.as_path().components()
    }

    pub fn to_path_buf(&self) -> PathBuf {
        self.as_path().to_path_buf()
    }

    pub fn display(&self) -> std::path::Display {
        self.as_path().display()
    }

    
    pub fn to_owned(&self) -> RepoPath<PathBuf> {
        RepoPath(self.0.as_ref().to_path_buf())
    }    
    
    pub fn join(&self, path: &Path) -> RepoPath<PathBuf> {
        // TODO: check that the joined path is indeed inside the repo
        let joined_path = self.as_path().join(path);
        RepoPath(joined_path)
    }
}

impl<P: AsRef<Path>> RepoPath<P> {
    pub fn empty(&self) -> bool {
        self.as_path() == Path::new("")
    }
}

/// A directory at the root of a pijul repository.
#[derive(Clone, Copy, Debug)]
pub struct RepoRoot<P: AsRef<Path>> {
    pub repo_root: P,
}

/// Name of the root directory, i.e. `.pijul`.
pub const PIJUL_DIR_NAME: &'static str = ".pijul";

/// Basename of the changes file for branch `br`. This file is only
/// used when pulling/pushing over HTTP (where calling remote programs
/// to list patches is impossible).
///
/// The changes file contains the same information as the one returned by `pijul log --hash-only`.
pub fn branch_changes_base_path(b: &str) -> String {
    "changes.".to_string() + &bs58::encode(b.as_bytes()).into_string()
}

/// Basename of the patch corresponding to the given patch hash.
pub fn patch_file_name(hash: HashRef) -> String {
    hash.to_base58() + ".gz"
}

impl<P: AsRef<Path>> RepoRoot<P> {
    /// The subdirectory of `self` with pijul's metadata
    pub fn repo_dir(&self) -> PathBuf {
        self.repo_root.as_ref().join(PIJUL_DIR_NAME)
    }

    /// Directory where the pristine of `self` is.
    /// For instance, if the repository in in `/a/b`,
    /// `self.pristine_dir() is `/a/b/.pijul/pristine`.
    pub fn pristine_dir(&self) -> PathBuf {
        self.repo_dir().join("pristine")
    }

    /// Directory where the patches are. `patches_dir("/a/b") = "/a/b/.pijul/patches"`.
    pub fn patches_dir(&self) -> PathBuf {
        self.repo_dir().join("patches")
    }

    /// The location of the changes file for theb branch `b`.
    ///
    /// The changes file contains the same information as the one returned by `pijul log --hash-only`.
    pub fn branch_changes_file(&self, b: &str) -> PathBuf {
        self.repo_dir().join(branch_changes_base_path(b))
    }

    /// The meta file, where user preferences are stored.
    pub fn meta_file(&self) -> PathBuf {
        self.repo_dir().join("meta.toml")
    }

    /// The id file is used for remote operations, to identify a
    /// repository and save bandwidth when the remote state is partially
    /// known.
    pub fn id_file(&self) -> PathBuf {
        self.repo_dir().join("id")
    }

    /// Read a complete patch.
    pub fn read_patch(&self, hash: HashRef) -> Result<Patch> {
        let patch_dir = self.patches_dir();
        let path = patch_dir.join(&patch_file_name(hash));
        let f = File::open(path)?;
        let mut f = BufReader::new(f);
        let (_, _, patch) = Patch::from_reader_compressed(&mut f)?;
        Ok(patch)
    }

    /// Read a patch, but without the "changes" part, i.e. the actual
    /// contents of the patch.
    pub fn read_patch_nochanges(&self, hash: HashRef) -> Result<PatchHeader> {
        let patch_dir = self.patches_dir();
        let path = patch_dir.join(&patch_file_name(hash));
        let f = File::open(path)?;
        let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
        Ok(PatchHeader::from_reader_nochanges(&mut f)?)
    }

    /// Read a patch, but without the "changes" part, i.e. the actual
    /// contents of the patch.
    pub fn read_dependencies(&self, hash: HashRef) -> Result<Vec<Hash>> {
        let patch_dir = self.patches_dir();
        let path = patch_dir.join(&patch_file_name(hash));
        let f = File::open(path)?;
        let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
        Ok(Patch::read_dependencies(&mut f)?)
    }

    /// The ignore file that is _not_ tracked by pijul.
    pub fn local_ignore_file(&self) -> PathBuf {
        self.repo_dir().join("local").join("ignore")
    }

    pub fn get_current_branch(&self) -> Result<String> {
        let mut path = self.repo_dir();
        path.push("current_branch");
        if let Ok(mut f) = File::open(&path) {
            let mut s = String::new();
            f.read_to_string(&mut s)?;
            Ok(s.trim().to_string())
        } else {
            Ok(DEFAULT_BRANCH.to_string())
        }
    }

    pub fn set_current_branch(&self, branch: &str) -> Result<()> {
        let mut path = self.repo_dir();
        path.push("current_branch");
        let mut f = File::create(&path)?;
        f.write_all(branch.trim().as_ref())?;
        f.write_all(b"\n")?;
        Ok(())
    }

    pub fn open_repo(&self, increase: Option<u64>) -> Result<Repository> {
        Repository::open(self.pristine_dir(), increase)
    }

    
    pub fn relativize<'a>(&self, path: &'a Path) -> Result<RepoPath<&'a Path>> {
    	match path.strip_prefix(&self.repo_root) {
            Ok(p) => Ok(RepoPath(p)),
            Err(_) => Err(Error::FileNotInRepo(path.to_path_buf()))
        }
    }

    pub fn absolutize<'a>(&self, path: &RepoPath<impl AsRef<Path>>) -> PathBuf {
        self.repo_root.as_ref().join(path.as_path())
    }
}

impl<P: AsRef<Path> + 'static> RepoRoot<P> {
    pub fn untracked_files<T: rand::Rng, Q: AsRef<Path>>(
        &self,
        txn: &MutTxn<T>,
        path: Q,
    ) -> impl Iterator<Item = RepoPath<PathBuf>> + '_ {
        let known_files = txn.list_files(ROOT_INODE).unwrap_or_else(|_| vec![]);

        let o = OverrideBuilder::new(self.repo_root.as_ref())
            .add("!.pijul")
            .unwrap()
            .build()
            .unwrap(); // we can be pretty confident these two calls will
                       // not fail as the glob is hard-coded

        let mut w = WalkBuilder::new(path.as_ref());
        w.git_ignore(false)
            .git_exclude(false)
            .git_global(false)
            .hidden(false)
            .add_custom_ignore_filename(".pijulignore");

        // add .pijul/local/ignore
        w.add_ignore(self.local_ignore_file());
        w.overrides(o);

        w.build().filter_map(move |f| {
            if let Ok(f) = f {
                let p = f.path();
                if p == self.repo_root.as_ref() {
                    return None;
                }

                let p_in_repo = self.relativize(&p).unwrap();
                // ^- cannot fail since p must be within repo_root.
                if known_files.iter().any(|t| t.as_ref() == p_in_repo) {
                    return None
                }
                Some(p_in_repo.to_owned())
            } else {
                None
            }
        })
    }
}

/// Find the repository root from one of its descendant
/// directories. Return `None` iff `dir` is not in a repository.
pub fn find_repo_root<'a>(dir: &'a Path) -> Option<RepoRoot<PathBuf>> {
    let mut p = dir.to_path_buf();
    loop {
        p.push(PIJUL_DIR_NAME);
        match metadata(&p) {
            Ok(ref attr) if attr.is_dir() => {
                p.pop();
                return Some(RepoRoot { repo_root: p });
            }
            _ => {}
        }
        p.pop();

        if !p.pop() {
            return None;
        }
    }
}

#[doc(hidden)]
pub const ID_LENGTH: usize = 100;

/// Create a repository. `dir` must be the repository root (a
/// `".pijul"` directory will be created in `dir`).
pub fn create<R: Rng>(dir: &Path, mut rng: R) -> std::io::Result<RepoRoot<PathBuf>> {
    let r = RepoRoot {
        repo_root: canonicalize(dir)?,
    };

    let mut repo_dir = r.repo_dir();
    create_dir_all(&repo_dir)?;

    repo_dir.push("pristine");
    create_dir_all(&repo_dir)?;
    repo_dir.pop();

    repo_dir.push("patches");
    create_dir_all(&repo_dir)?;
    repo_dir.pop();

    repo_dir.push("id");
    let mut f = std::fs::File::create(&repo_dir)?;
    let mut x = String::new();
    x.extend(rng.sample_iter(&Alphanumeric).take(ID_LENGTH));
    f.write_all(x.as_bytes())?;
    repo_dir.pop();

    repo_dir.push("version");
    let mut f = std::fs::File::create(&repo_dir)?;
    writeln!(f, "{}", env!("CARGO_PKG_VERSION"))?;
    repo_dir.pop();

    repo_dir.push("local");
    create_dir_all(&repo_dir)?;
    repo_dir.pop();

    repo_dir.push("hooks");
    create_dir_all(&repo_dir)?;
    repo_dir.pop();

    repo_dir.push("local");
    repo_dir.push("ignore");
    std::fs::File::create(&repo_dir)?;
    repo_dir.pop();
    repo_dir.pop();

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    Ok(r)
}

================================

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Ok(())
    Ok(())

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
pub fn open_repo(root: &Path, increase: Option<u64>) -> Result<Repository> {

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Repository::open(pristine_dir(root), increase)

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}

1
2


3
use libpijul::fs_representation::{get_current_branch, set_current_branch};
use clap::{Arg, ArgMatches, SubCommand};

use super::{default_explain, get_current_branch, set_current_branch, BasicOptions,
            StaticSubcommand};
use super::{default_explain, BasicOptions, StaticSubcommand};




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
use commands::{assert_no_containing_repo, create_repo, default_explain,
use libpijul::fs_representation::set_current_branch;
            set_current_branch(path, args.to_branch).map_err(|x| x.into())
                    set_current_branch(path, args.to_branch).map_err(|x| x.into())
use clap::{Arg, ArgMatches, SubCommand};
use commands::remote::{parse_remote, Remote};
use commands::{assert_no_containing_repo, create_repo, default_explain, StaticSubcommand};
use error::Error;
use libpijul::fs_representation::{RepoPath, RepoRoot};
use libpijul::{Hash, DEFAULT_BRANCH};
use regex::Regex;
use std::io::{stderr, Write};
use std::path::Path;
use std::process::exit;
use tempfile::tempdir_in;

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("clone")
        .about("Clone a remote branch")
        .arg(
            Arg::with_name("from")
                .help("Repository to clone.")
                .required(true),
        )
        .arg(
            Arg::with_name("from_branch")
                .long("from-branch")
                .help("The branch to pull from")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("to_branch")
                .long("to-branch")
                .help("The branch to pull into")
                .takes_value(true),
        )
        .arg(Arg::with_name("to").help("Target."))
        .arg(
            Arg::with_name("from_path")
                .long("path")
                .help("Only pull patches relative to that path.")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("patch")
                .long("patch")
                .help("Pull a patch and its dependencies.")
                .takes_value(true)
                .multiple(true),
        )
        .arg(
            Arg::with_name("port")
                .short("p")
                .long("port")
                .help("Port of the remote ssh server.")
                .takes_value(true)
                .validator(|val| {
                    let x: Result<u16, _> = val.parse();
                    match x {
                        Ok(_) => Ok(()),
                        Err(_) => Err(val),
                    }
                }),
        );
}
#[derive(Debug)]
pub struct Params<'a> {
    pub from: Remote<'a>,
    pub from_branch: &'a str,
    pub from_path: Vec<RepoPath<&'a Path>>,
    pub to: Remote<'a>,
    pub to_branch: &'a str,
}

pub fn parse_args<'a>(args: &'a ArgMatches) -> Params<'a> {
    // At least one must not use its "port" argument
    let from = parse_remote(
        args.value_of("from").unwrap(),
        args.value_of("port").and_then(|x| Some(x.parse().unwrap())),
        None,
        None,
    );
    let to = if let Some(to) = args.value_of("to") {
        parse_remote(
            to,
            args.value_of("port").and_then(|x| Some(x.parse().unwrap())),
            None,
            None,
        )
    } else {
        let basename = Regex::new(r"([^/:]+)").unwrap();
        let from = args.value_of("from").unwrap();
        if let Some(to) = basename.captures_iter(from).last().and_then(|to| to.get(1)) {
            parse_remote(
                to.as_str(),
                args.value_of("port").and_then(|x| Some(x.parse().unwrap())),
                None,
                None,
            )
        } else {
            panic!("Could not parse target")
        }
    };
    let from_branch = args.value_of("from_branch").unwrap_or(DEFAULT_BRANCH);
    let from_path = args
        .values_of("from_path")
        .map(|x| x.map(|p| RepoPath(Path::new(p))).collect())
        .unwrap_or(Vec::new());
    let to_branch = args.value_of("to_branch").unwrap_or(from_branch);
    Params {
        from,
        from_branch,
        from_path,
        to,
        to_branch,
    }
}

pub fn run(args_: &ArgMatches) -> Result<(), Error> {
    let args = parse_args(args_);
    debug!("{:?}", args);
    match args.to {
        Remote::Local { path: repo_root } => {
            assert_no_containing_repo(&repo_root.repo_root)?;

            let parent = repo_root.repo_root.parent().unwrap();
            let tmp_dir = tempdir_in(parent)?;
            {
                create_repo(tmp_dir.path())?;
                let tmp_root = RepoRoot {
                    repo_root: tmp_dir.path(),
                };
                let mut session = args.from.session()?;
                let mut pullable: Vec<_> = if let Some(patches) = args_.values_of("patch") {
                    let mut p = Vec::new();
                    for x in patches {
                        p.push((Hash::from_base58(x).unwrap(), 0))
                    }
                    p
                } else {
                    session.changes(args.from_branch, &args.from_path[..])?
                };
                session.pull(
                    &tmp_root,
                    args.to_branch,
                    &mut pullable,
                    &args.from_path,
                    true,
                )?;
                tmp_root.set_current_branch(args.to_branch)?;
            }
            let path = tmp_dir.into_path();
            std::fs::rename(&path, &repo_root.repo_root)?;
            Ok(())
        }
        _ => {
            // Clone between remote repositories.
            match args.from {
                Remote::Local { path } => {
                    let mut to_session = args.to.session()?;
                    debug!("remote init");
                    to_session.remote_init()?;
                    debug!("pushable?");
                    let pushable = to_session.pushable_patches(
                        args.from_branch,
                        args.to_branch,
                        &path,
                        &args.from_path,
                    )?;
                    debug!("pushable = {:?}", pushable);
                    let pushable = pushable.pushable.into_iter().map(|(h, _, _)| h).collect();
                    to_session.push(&path, args.to_branch, pushable)?;
                    path.set_current_branch(args.to_branch)
                        .map_err(|x| x.into())
                }


1
2
3
4
5
6
7
8

use libpijul::fs_representation::{read_patch, set_current_branch};
use super::{BasicOptions, StaticSubcommand, default_explain};
use clap::{Arg, ArgGroup, ArgMatches, SubCommand};
use commands::checkout::checkout;
use libpijul::fs_representation::RepoRoot;
use libpijul::{apply_resize_no_output, Hash};
use rand;
use std::mem;
use std::path::Path; // PathBuf;

use super::{BasicOptions, StaticSubcommand, set_current_branch, default_explain};



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

























use std::fs::{canonicalize, metadata, create_dir};
use libpijul::fs_representation::get_current_branch;
use std::io::{Write, stderr};
use clap;
use clap::ArgMatches;
pub type StaticSubcommand = clap::App<'static, 'static>;

mod ask;
mod fs_operation;
pub mod remote;
mod ssh_auth_attempts;

pub mod add;
pub mod apply;
pub mod branches;
pub mod checkout;
pub mod clone;
pub mod credit;
pub mod dependencies;
pub mod diff;
pub mod dist;
pub mod fork;
pub mod generate_completions;
pub mod grep;
pub mod hooks;
pub mod info;
pub mod init;
pub mod key;
pub mod log;
pub mod ls;
pub mod mv;
pub mod patch;
pub mod prune;
pub mod pull;
pub mod push;
pub mod record;
pub mod remove;
pub mod revert;
pub mod rollback;
pub mod sign;
pub mod status;
pub mod tag;
pub mod unrecord;

mod fold_until;

use error::Error;
use libpijul::fs_representation::{RepoPath, RepoRoot};
use libpijul::Hash;
use libpijul::{fs_representation, Inode, Repository, Txn, DEFAULT_BRANCH};
use rand;
use std::borrow::Cow;
use std::env::current_dir;
use std::env::var;
use std::fs::{canonicalize, create_dir, metadata};
use std::io::{stderr, Write};
use std::path::{Path, PathBuf};
use std::process::exit;

pub fn all_command_invocations() -> Vec<StaticSubcommand> {
    return vec![
        log::invocation(),
        info::invocation(),
        init::invocation(),
        record::invocation(),
        unrecord::invocation(),
        add::invocation(),
        pull::invocation(),
        push::invocation(),
        apply::invocation(),
        clone::invocation(),
        remove::invocation(),
        mv::invocation(),
        ls::invocation(),
        revert::invocation(),
        patch::invocation(),
        fork::invocation(),
        branches::invocation(),
        prune::invocation(),
        checkout::invocation(),
        diff::invocation(),
        credit::invocation(),
        dist::invocation(),
        key::invocation(),
        rollback::invocation(),
        status::invocation(),
        dependencies::invocation(),
        tag::invocation(),
        sign::invocation(),
        generate_completions::invocation(),
        grep::invocation(),
    ];
}

pub fn get_wd(repository_path: Option<&Path>) -> Result<PathBuf, Error> {
    debug!("get_wd: {:?}", repository_path);
    match repository_path {
        None => Ok(canonicalize(current_dir()?)?),
        Some(a) if a.is_relative() => Ok(canonicalize(current_dir()?.join(a))?),
        Some(a) => Ok(canonicalize(a)?),
    }
}

pub fn get_current_branch(root: &Path) -> Result<String> {
    debug!("path: {:?}", root);
    let mut path = fs_representation::repo_dir(&canonicalize(root)?);
    path.push("current_branch");
    path.push("current_branch");
    if let Ok(mut f) = File::open(&path) {
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s.trim().to_string())
    } else {
        Ok(DEFAULT_BRANCH.to_string())
    }
}

pub fn set_current_branch(root: &Path, branch: &str) -> Result<()> {
    debug!("set current branch: root={:?}, branch={:?}", root, branch);
    let mut path = fs_representation::repo_dir(&canonicalize(root)?);
    path.push("current_branch");
    path.push("current_branch");
    let mut f = File::create(&path)?;
    f.write_all(branch.trim().as_ref())?;
    f.write_all(b"\n")?;
    Ok(())