main.rs

   1extern crate ansi_term;
   2extern crate chrono;
   3#[macro_use]
   4extern crate clap;
   5extern crate colorparse;
   6extern crate git2;
   7extern crate isatty;
   8extern crate munkres;
   9#[macro_use]
  10extern crate quick_error;
  11extern crate tempdir;
  12
  13use std::cmp::max;
  14use std::env;
  15use std::ffi::{OsStr, OsString};
  16use std::fmt::Write as FmtWrite;
  17use std::fs::File;
  18use std::io::Read;
  19use std::io::Write as IoWrite;
  20use std::process::Command;
  21use ansi_term::Style;
  22use chrono::offset::TimeZone;
  23use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
  24use git2::{Config, Commit, Delta, Diff, Object, ObjectType, Oid, Reference, Repository, TreeBuilder};
  25use tempdir::TempDir;
  26
  27quick_error! {
  28    #[derive(Debug)]
  29    enum Error {
  30        Git2(err: git2::Error) {
  31            from()
  32            cause(err)
  33            display("{}", err)
  34        }
  35        IO(err: std::io::Error) {
  36            from()
  37            cause(err)
  38            display("{}", err)
  39        }
  40        Msg(msg: String) {
  41            from()
  42            from(s: &'static str) -> (s.to_string())
  43            description(msg)
  44            display("{}", msg)
  45        }
  46        Utf8Error(err: std::str::Utf8Error) {
  47            from()
  48            cause(err)
  49            display("{}", err)
  50        }
  51    }
  52}
  53
  54type Result<T> = std::result::Result<T, Error>;
  55
  56const COMMIT_MESSAGE_COMMENT: &'static str = "
  57# Please enter the commit message for your changes. Lines starting
  58# with '#' will be ignored, and an empty message aborts the commit.
  59";
  60const COVER_LETTER_COMMENT: &'static str = "
  61# Please enter the cover letter for your changes. Lines starting
  62# with '#' will be ignored, and an empty message aborts the change.
  63";
  64const REBASE_COMMENT: &'static str = "\
  65#
  66# Commands:
  67# p, pick = use commit
  68# r, reword = use commit, but edit the commit message
  69# e, edit = use commit, but stop for amending
  70# s, squash = use commit, but meld into previous commit
  71# f, fixup = like \"squash\", but discard this commit's log message
  72# x, exec = run command (the rest of the line) using shell
  73# d, drop = remove commit
  74#
  75# These lines can be re-ordered; they are executed from top to bottom.
  76#
  77# If you remove a line here THAT COMMIT WILL BE LOST.
  78#
  79# However, if you remove everything, the rebase will be aborted.
  80";
  81const SCISSOR_LINE: &'static str = "\
  82# ------------------------ >8 ------------------------";
  83const SCISSOR_COMMENT: &'static str = "\
  84# Do not touch the line above.
  85# Everything below will be removed.
  86";
  87
  88const SHELL_METACHARS: &'static str = "|&;<>()$`\\\"' \t\n*?[#~=%";
  89
  90const SERIES_PREFIX: &'static str = "refs/heads/git-series/";
  91const SHEAD_REF: &'static str = "refs/SHEAD";
  92const STAGED_PREFIX: &'static str = "refs/git-series-internals/staged/";
  93const WORKING_PREFIX: &'static str = "refs/git-series-internals/working/";
  94
  95const GIT_FILEMODE_BLOB: u32 = 0o100644;
  96const GIT_FILEMODE_COMMIT: u32 = 0o160000;
  97
  98fn zero_oid() -> Oid {
  99    Oid::from_bytes(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00").unwrap()
 100}
 101
 102fn peel_to_commit(r: Reference) -> Result<Commit> {
 103    Ok(try!(try!(r.peel(ObjectType::Commit)).into_commit().map_err(|obj| format!("Internal error: expected a commit: {}", obj.id()))))
 104}
 105
 106fn commit_obj_summarize_components(commit: &mut Commit) -> Result<(String, String)> {
 107    let short_id_buf = try!(commit.as_object().short_id());
 108    let short_id = short_id_buf.as_str().unwrap();
 109    let summary = String::from_utf8_lossy(commit.summary_bytes().unwrap());
 110    Ok((short_id.to_string(), summary.to_string()))
 111}
 112
 113fn commit_summarize_components(repo: &Repository, id: Oid) -> Result<(String, String)> {
 114    let mut commit = try!(repo.find_commit(id));
 115    commit_obj_summarize_components(&mut commit)
 116}
 117
 118fn commit_obj_summarize(commit: &mut Commit) -> Result<String> {
 119    let (short_id, summary) = try!(commit_obj_summarize_components(commit));
 120    Ok(format!("{} {}", short_id, summary))
 121}
 122
 123fn commit_summarize(repo: &Repository, id: Oid) -> Result<String> {
 124    let mut commit = try!(repo.find_commit(id));
 125    commit_obj_summarize(&mut commit)
 126}
 127
 128fn notfound_to_none<T>(result: std::result::Result<T, git2::Error>) -> Result<Option<T>> {
 129    match result {
 130        Err(ref e) if e.code() == git2::ErrorCode::NotFound => Ok(None),
 131        Err(e) => Err(e.into()),
 132        Ok(x) => Ok(Some(x)),
 133    }
 134}
 135
 136// If current_id_opt is Some, acts like reference_matching.  If current_id_opt is None, acts like
 137// reference.
 138fn reference_matching_opt<'repo>(repo: &'repo Repository, name: &str, id: Oid, force: bool, current_id_opt: Option<Oid>, log_message: &str) -> Result<Reference<'repo>> {
 139    match current_id_opt {
 140        None => Ok(try!(repo.reference(name, id, force, log_message))),
 141        Some(current_id) => Ok(try!(repo.reference_matching(name, id, force, current_id, log_message))),
 142    }
 143}
 144
 145fn parents_from_ids(repo: &Repository, mut parents: Vec<Oid>) -> Result<Vec<Commit>> {
 146    parents.sort();
 147    parents.dedup();
 148    parents.drain(..).map(|id| Ok(try!(repo.find_commit(id)))).collect::<Result<Vec<Commit>>>()
 149}
 150
 151struct Internals<'repo> {
 152    staged: TreeBuilder<'repo>,
 153    working: TreeBuilder<'repo>,
 154}
 155
 156impl<'repo> Internals<'repo> {
 157    fn read(repo: &'repo Repository) -> Result<Self> {
 158        let shead = try!(repo.find_reference(SHEAD_REF));
 159        let series_name = try!(shead_series_name(&shead));
 160        let mut internals = try!(Internals::read_series(repo, &series_name));
 161        try!(internals.update_series(repo));
 162        Ok(internals)
 163    }
 164
 165    fn read_series(repo: &'repo Repository, series_name: &str) -> Result<Self> {
 166        let committed_id = try!(notfound_to_none(repo.refname_to_id(&format!("{}{}", SERIES_PREFIX, series_name))));
 167        let maybe_get_ref = |prefix: &str| -> Result<TreeBuilder<'repo>> {
 168            match try!(notfound_to_none(repo.refname_to_id(&format!("{}{}", prefix, series_name)))).or(committed_id) {
 169                Some(id) => {
 170                    let c = try!(repo.find_commit(id));
 171                    let t = try!(c.tree());
 172                    Ok(try!(repo.treebuilder(Some(&t))))
 173                }
 174                None => Ok(try!(repo.treebuilder(None))),
 175            }
 176        };
 177        Ok(Internals {
 178            staged: try!(maybe_get_ref(STAGED_PREFIX)),
 179            working: try!(maybe_get_ref(WORKING_PREFIX)),
 180        })
 181    }
 182
 183    fn exists(repo: &'repo Repository, series_name: &str) -> Result<bool> {
 184        for prefix in [SERIES_PREFIX, STAGED_PREFIX, WORKING_PREFIX].iter() {
 185            let prefixed_name = format!("{}{}", prefix, series_name);
 186            if try!(notfound_to_none(repo.refname_to_id(&prefixed_name))).is_some() {
 187                return Ok(true);
 188            }
 189        }
 190        Ok(false)
 191    }
 192
 193    // Returns true if it had anything to copy.
 194    fn copy(repo: &'repo Repository, source: &str, dest: &str) -> Result<bool> {
 195        let mut copied_any = false;
 196        for prefix in [SERIES_PREFIX, STAGED_PREFIX, WORKING_PREFIX].iter() {
 197            let prefixed_source = format!("{}{}", prefix, source);
 198            if let Some(r) = try!(notfound_to_none(repo.find_reference(&prefixed_source))) {
 199                let oid = try!(r.target().ok_or(format!("Internal error: \"{}\" is a symbolic reference", prefixed_source)));
 200                let prefixed_dest = format!("{}{}", prefix, dest);
 201                try!(repo.reference(&prefixed_dest, oid, false, &format!("copied from {}", prefixed_source)));
 202                copied_any = true;
 203            }
 204        }
 205        Ok(copied_any)
 206    }
 207
 208    // Returns true if it had anything to delete.
 209    fn delete(repo: &'repo Repository, series_name: &str) -> Result<bool> {
 210        let mut deleted_any = false;
 211        for prefix in [SERIES_PREFIX, STAGED_PREFIX, WORKING_PREFIX].iter() {
 212            let prefixed_name = format!("{}{}", prefix, series_name);
 213            if let Some(mut r) = try!(notfound_to_none(repo.find_reference(&prefixed_name))) {
 214                try!(r.delete());
 215                deleted_any = true;
 216            }
 217        }
 218        Ok(deleted_any)
 219    }
 220
 221    fn update_series(&mut self, repo: &'repo Repository) -> Result<()> {
 222        let head_id = try!(repo.refname_to_id("HEAD"));
 223        try!(self.working.insert("series", head_id, GIT_FILEMODE_COMMIT as i32));
 224        Ok(())
 225    }
 226
 227    fn write(&self, repo: &'repo Repository) -> Result<()> {
 228        let config = try!(repo.config());
 229        let author = try!(get_signature(&config, "AUTHOR"));
 230        let committer = try!(get_signature(&config, "COMMITTER"));
 231
 232        let shead = try!(repo.find_reference(SHEAD_REF));
 233        let series_name = try!(shead_series_name(&shead));
 234        let maybe_commit = |prefix: &str, tb: &TreeBuilder| -> Result<()> {
 235            let tree_id = try!(tb.write());
 236            let refname = format!("{}{}", prefix, series_name);
 237            let old_commit_id = try!(notfound_to_none(repo.refname_to_id(&refname)));
 238            if let Some(id) = old_commit_id {
 239                let c = try!(repo.find_commit(id));
 240                if c.tree_id() == tree_id {
 241                    return Ok(());
 242                }
 243            }
 244            let tree = try!(repo.find_tree(tree_id));
 245            let mut parents = Vec::new();
 246            // Include all commits from tree, to keep them reachable and fetchable. Include base,
 247            // because series might not have it as an ancestor; we don't enforce that until commit.
 248            for e in tree.iter() {
 249                if e.kind() == Some(ObjectType::Commit) {
 250                    parents.push(e.id());
 251                }
 252            }
 253            let parents = try!(parents_from_ids(repo, parents));
 254            let parents_ref: Vec<&_> = parents.iter().collect();
 255            let commit_id = try!(repo.commit(None, &author, &committer, &refname, &tree, &parents_ref));
 256            try!(repo.reference_ensure_log(&refname));
 257            try!(reference_matching_opt(repo, &refname, commit_id, true, old_commit_id, &format!("commit: {}", refname)));
 258            Ok(())
 259        };
 260        try!(maybe_commit(STAGED_PREFIX, &self.staged));
 261        try!(maybe_commit(WORKING_PREFIX, &self.working));
 262        Ok(())
 263    }
 264}
 265
 266fn diff_empty(diff: &Diff) -> bool {
 267    diff.deltas().len() == 0
 268}
 269
 270fn add(repo: &Repository, m: &ArgMatches) -> Result<()> {
 271    let mut internals = try!(Internals::read(repo));
 272    for file in m.values_of_os("change").unwrap() {
 273        match try!(internals.working.get(file)) {
 274            Some(entry) => { try!(internals.staged.insert(file, entry.id(), entry.filemode())); }
 275            None => {
 276                if try!(internals.staged.get(file)).is_some() {
 277                    try!(internals.staged.remove(file));
 278                }
 279            }
 280        }
 281    }
 282    internals.write(repo)
 283}
 284
 285fn unadd(repo: &Repository, m: &ArgMatches) -> Result<()> {
 286    let shead = try!(repo.find_reference(SHEAD_REF));
 287    let started = {
 288        let shead_target = try!(shead.symbolic_target().ok_or("SHEAD not a symbolic reference"));
 289        try!(notfound_to_none(repo.find_reference(shead_target))).is_some()
 290    };
 291
 292    let mut internals = try!(Internals::read(repo));
 293    if started {
 294        let shead_commit = try!(peel_to_commit(shead));
 295        let shead_tree = try!(shead_commit.tree());
 296
 297        for file in m.values_of("change").unwrap() {
 298            match shead_tree.get_name(file) {
 299                Some(entry) => {
 300                    try!(internals.staged.insert(file, entry.id(), entry.filemode()));
 301                }
 302                None => { try!(internals.staged.remove(file)); }
 303            }
 304        }
 305    } else {
 306        for file in m.values_of("change").unwrap() {
 307            try!(internals.staged.remove(file))
 308        }
 309    }
 310    internals.write(repo)
 311}
 312
 313fn shead_series_name(shead: &Reference) -> Result<String> {
 314    let shead_target = try!(shead.symbolic_target().ok_or("SHEAD not a symbolic reference"));
 315    if !shead_target.starts_with(SERIES_PREFIX) {
 316        return Err(format!("SHEAD does not start with {}", SERIES_PREFIX).into());
 317    }
 318    Ok(shead_target[SERIES_PREFIX.len()..].to_string())
 319}
 320
 321fn series(out: &mut Output, repo: &Repository) -> Result<()> {
 322    let mut refs = Vec::new();
 323    for prefix in [SERIES_PREFIX, STAGED_PREFIX, WORKING_PREFIX].iter() {
 324        let l = prefix.len();
 325        for r in try!(repo.references_glob(&[prefix, "*"].concat())).names() {
 326            refs.push(try!(r)[l..].to_string());
 327        }
 328    }
 329    let shead_target = if let Some(shead) = try!(notfound_to_none(repo.find_reference(SHEAD_REF))) {
 330        Some(try!(shead_series_name(&shead)))
 331    } else {
 332        None
 333    };
 334    refs.extend(shead_target.clone().into_iter());
 335    refs.sort();
 336    refs.dedup();
 337
 338    let config = try!(try!(repo.config()).snapshot());
 339    try!(out.auto_pager(&config, "branch", false));
 340    let color_current = try!(out.get_color(&config, "branch", "current", "green"));
 341    let color_plain = try!(out.get_color(&config, "branch", "plain", "normal"));
 342    for name in refs.iter() {
 343        let (star, color) = if Some(name) == shead_target.as_ref() {
 344            ('*', color_current)
 345        } else {
 346            (' ', color_plain)
 347        };
 348        let new = if try!(notfound_to_none(repo.refname_to_id(&format!("{}{}", SERIES_PREFIX, name)))).is_none() {
 349            " (new, no commits yet)"
 350        } else {
 351            ""
 352        };
 353        try!(writeln!(out, "{} {}{}", star, color.paint(name as &str), new));
 354    }
 355    if refs.is_empty() {
 356        try!(writeln!(out, "No series; use \"git series start <name>\" to start"));
 357    }
 358    Ok(())
 359}
 360
 361fn start(repo: &Repository, m: &ArgMatches) -> Result<()> {
 362    let head = try!(repo.head());
 363    let head_commit = try!(peel_to_commit(head));
 364    let head_id = head_commit.as_object().id();
 365
 366    let name = m.value_of("name").unwrap();
 367    if try!(Internals::exists(repo, name)) {
 368        return Err(format!("Series {} already exists.\nUse checkout to resume working on an existing patch series.", name).into());
 369    }
 370    let prefixed_name = &[SERIES_PREFIX, name].concat();
 371    try!(repo.reference_symbolic(SHEAD_REF, &prefixed_name, true, &format!("git series start {}", name)));
 372
 373    let internals = try!(Internals::read(repo));
 374    try!(internals.write(repo));
 375
 376    // git status parses this reflog string; the prefix must remain "checkout: moving from ".
 377    try!(repo.reference("HEAD", head_id, true, &format!("checkout: moving from {} to {} (git series start {})", head_id, head_id, name)));
 378    println!("HEAD is now detached at {}", try!(commit_summarize(&repo, head_id)));
 379    Ok(())
 380}
 381
 382fn checkout_tree(repo: &Repository, treeish: &Object) -> Result<()> {
 383    let mut conflicts = Vec::new();
 384    let mut dirty = Vec::new();
 385    let result = {
 386        let mut opts = git2::build::CheckoutBuilder::new();
 387        opts.safe();
 388        opts.notify_on(git2::CHECKOUT_NOTIFICATION_CONFLICT | git2::CHECKOUT_NOTIFICATION_DIRTY);
 389        opts.notify(|t, path, _, _, _| {
 390            let path = path.unwrap().to_owned();
 391            if t == git2::CHECKOUT_NOTIFICATION_CONFLICT {
 392                conflicts.push(path);
 393            } else if t == git2::CHECKOUT_NOTIFICATION_DIRTY {
 394                dirty.push(path);
 395            }
 396            true
 397        });
 398        if isatty::stdout_isatty() {
 399            opts.progress(|_, completed, total| {
 400                let total = total.to_string();
 401                print!("\rChecking out files: {1:0$}/{2}", total.len(), completed, total);
 402            });
 403        }
 404        repo.checkout_tree(treeish, Some(&mut opts))
 405    };
 406    match result {
 407        Err(ref e) if e.code() == git2::ErrorCode::Conflict => {
 408            let mut msg = String::new();
 409            writeln!(msg, "error: Your changes to the following files would be overwritten by checkout:").unwrap();
 410            for path in conflicts {
 411                writeln!(msg, "        {}", path.to_string_lossy()).unwrap();
 412            }
 413            writeln!(msg, "Please, commit your changes or stash them before you switch series.").unwrap();
 414            return Err(msg.into());
 415        }
 416        _ => try!(result),
 417    }
 418    println!("");
 419    if !dirty.is_empty() {
 420        let mut stderr = std::io::stderr();
 421        writeln!(stderr, "Files with changes unaffected by checkout:").unwrap();
 422        for path in dirty {
 423            writeln!(stderr, "        {}", path.to_string_lossy()).unwrap();
 424        }
 425    }
 426    Ok(())
 427}
 428
 429fn checkout(repo: &Repository, m: &ArgMatches) -> Result<()> {
 430    match repo.state() {
 431        git2::RepositoryState::Clean => (),
 432        s => { return Err(format!("{:?} in progress; cannot checkout patch series", s).into()); }
 433    }
 434    let name = m.value_of("name").unwrap();
 435    if !try!(Internals::exists(repo, name)) {
 436        return Err(format!("Series {} does not exist.\nUse \"git series start <name>\" to start a new patch series.", name).into());
 437    }
 438
 439    let internals = try!(Internals::read_series(repo, name));
 440    let new_head_id = try!(try!(internals.working.get("series")).ok_or(format!("Could not find \"series\" in \"{}\"", name))).id();
 441    let new_head = try!(repo.find_commit(new_head_id)).into_object();
 442
 443    try!(checkout_tree(repo, &new_head));
 444
 445    let head = try!(repo.head());
 446    let head_commit = try!(peel_to_commit(head));
 447    let head_id = head_commit.as_object().id();
 448    println!("Previous HEAD position was {}", try!(commit_summarize(&repo, head_id)));
 449
 450    let prefixed_name = &[SERIES_PREFIX, name].concat();
 451    try!(repo.reference_symbolic(SHEAD_REF, &prefixed_name, true, &format!("git series checkout {}", name)));
 452    try!(internals.write(repo));
 453
 454    // git status parses this reflog string; the prefix must remain "checkout: moving from ".
 455    try!(repo.reference("HEAD", new_head_id, true, &format!("checkout: moving from {} to {} (git series checkout {})", head_id, new_head_id, name)));
 456    println!("HEAD is now detached at {}", try!(commit_summarize(&repo, new_head_id)));
 457
 458    Ok(())
 459}
 460
 461fn base(repo: &Repository, m: &ArgMatches) -> Result<()> {
 462    let mut internals = try!(Internals::read(repo));
 463
 464    let current_base_id = match try!(internals.working.get("base")) {
 465        Some(entry) => entry.id(),
 466        _ => zero_oid(),
 467    };
 468
 469    if !m.is_present("delete") && !m.is_present("base") {
 470        if current_base_id.is_zero() {
 471            return Err("Patch series has no base set".into());
 472        } else {
 473            println!("{}", current_base_id);
 474            return Ok(());
 475        }
 476    }
 477
 478    let new_base_id = if m.is_present("delete") {
 479        zero_oid()
 480    } else {
 481        let base = m.value_of("base").unwrap();
 482        let base_object = try!(repo.revparse_single(base));
 483        let base_commit = try!(base_object.peel(ObjectType::Commit));
 484        let base_id = base_commit.id();
 485        let s_working_series = try!(try!(internals.working.get("series")).ok_or("Could not find entry \"series\" in working vesion of current series"));
 486        if base_id != s_working_series.id() && !try!(repo.graph_descendant_of(s_working_series.id(), base_id)) {
 487            return Err(format!("Cannot set base to {}: not an ancestor of the patch series {}", base, s_working_series.id()).into());
 488        }
 489        base_id
 490    };
 491
 492    if current_base_id == new_base_id {
 493        println!("Base unchanged");
 494        return Ok(());
 495    }
 496
 497    if !current_base_id.is_zero() {
 498        println!("Previous base was {}", try!(commit_summarize(&repo, current_base_id)));
 499    }
 500
 501    if new_base_id.is_zero() {
 502        try!(internals.working.remove("base"));
 503        try!(internals.write(repo));
 504        println!("Cleared patch series base");
 505    } else {
 506        try!(internals.working.insert("base", new_base_id, GIT_FILEMODE_COMMIT as i32));
 507        try!(internals.write(repo));
 508        println!("Set patch series base to {}", try!(commit_summarize(&repo, new_base_id)));
 509    }
 510
 511    Ok(())
 512}
 513
 514fn detach(repo: &Repository) -> Result<()> {
 515    match repo.find_reference(SHEAD_REF) {
 516        Ok(mut r) => try!(r.delete()),
 517        Err(_) => { return Err("No current patch series to detach from.".into()); }
 518    }
 519    Ok(())
 520}
 521
 522fn delete(repo: &Repository, m: &ArgMatches) -> Result<()> {
 523    let name = m.value_of("name").unwrap();
 524    if let Ok(shead) = repo.find_reference(SHEAD_REF) {
 525        let shead_target = try!(shead_series_name(&shead));
 526        if shead_target == name {
 527            return Err(format!("Cannot delete the current series \"{}\"; detach first.", name).into());
 528        }
 529    }
 530    if !try!(Internals::delete(repo, name)) {
 531        return Err(format!("Nothing to delete: series \"{}\" does not exist.", name).into());
 532    }
 533    Ok(())
 534}
 535
 536fn do_diff(out: &mut Output, repo: &Repository) -> Result<()> {
 537    let internals = try!(Internals::read(&repo));
 538    let config = try!(try!(repo.config()).snapshot());
 539    try!(out.auto_pager(&config, "diff", true));
 540    let diffcolors = try!(DiffColors::new(out, &config));
 541
 542    let working_tree = try!(repo.find_tree(try!(internals.working.write())));
 543    let staged_tree = try!(repo.find_tree(try!(internals.staged.write())));
 544    let diff = try!(repo.diff_tree_to_tree(Some(&staged_tree), Some(&working_tree), None));
 545    try!(write_diff(out, &diffcolors, &diff, false));
 546
 547    let base1 = try!(internals.staged.get("base"));
 548    let series1 = try!(internals.staged.get("series"));
 549    let base2 = try!(internals.working.get("base"));
 550    let series2 = try!(internals.working.get("series"));
 551
 552    if let (Some(base1), Some(series1), Some(base2), Some(series2)) = (base1, series1, base2, series2) {
 553        try!(write_series_diff(out, repo, &diffcolors, (base1.id(), series1.id()), (base2.id(), series2.id())));
 554    } else {
 555        try!(writeln!(out, "Can't diff series: both versions must have base and series to diff"));
 556    }
 557
 558    Ok(())
 559}
 560
 561fn get_editor(config: &Config) -> Result<OsString> {
 562    if let Some(e) = env::var_os("GIT_EDITOR") {
 563        return Ok(e);
 564    }
 565    if let Ok(e) = config.get_path("core.editor") {
 566        return Ok(e.into());
 567    }
 568    let terminal_is_dumb = match env::var_os("TERM") {
 569        None => true,
 570        Some(t) => t.as_os_str() == "dumb",
 571    };
 572    if !terminal_is_dumb {
 573        if let Some(e) = env::var_os("VISUAL") {
 574            return Ok(e);
 575        }
 576    }
 577    if let Some(e) = env::var_os("EDITOR") {
 578        return Ok(e);
 579    }
 580    if terminal_is_dumb {
 581        return Err("TERM unset or \"dumb\" but EDITOR unset".into());
 582    }
 583    return Ok("vi".into());
 584}
 585
 586// Get the pager to use; with for_cmd set, get the pager for use by the
 587// specified git command.  If get_pager returns None, don't use a pager.
 588fn get_pager(config: &Config, for_cmd: &str, default: bool) -> Option<OsString> {
 589    if !isatty::stdout_isatty() {
 590        return None;
 591    }
 592    // pager.cmd can contain a boolean (if false, force no pager) or a
 593    // command-specific pager; only treat it as a command if it doesn't parse
 594    // as a boolean.
 595    let maybe_pager = config.get_path(&format!("pager.{}", for_cmd)).ok();
 596    let (cmd_want_pager, cmd_pager) = maybe_pager.map_or((default, None), |p|
 597            if let Ok(b) = Config::parse_bool(&p) {
 598                (b, None)
 599            } else {
 600                (true, Some(p))
 601            }
 602        );
 603    if !cmd_want_pager {
 604        return None;
 605    }
 606    let pager =
 607        if let Some(e) = env::var_os("GIT_PAGER") {
 608            Some(e)
 609        } else if let Some(p) = cmd_pager {
 610            Some(p.into())
 611        } else if let Ok(e) = config.get_path("core.pager") {
 612            Some(e.into())
 613        } else if let Some(e) = env::var_os("PAGER") {
 614            Some(e)
 615        } else {
 616            Some("less".into())
 617        };
 618    pager.and_then(|p| if p.is_empty() || p == OsString::from("cat") { None } else { Some(p) })
 619}
 620
 621/// Construct a Command, using the shell if the command contains shell metachars
 622fn cmd_maybe_shell<S: AsRef<OsStr>>(program: S, args: bool) -> Command {
 623    if program.as_ref().to_string_lossy().contains(|c| SHELL_METACHARS.contains(c)) {
 624        let mut cmd = Command::new("sh");
 625        cmd.arg("-c");
 626        if args {
 627            let mut program_with_args = program.as_ref().to_os_string();
 628            program_with_args.push(" \"$@\"");
 629            cmd.arg(program_with_args).arg(program);
 630        } else {
 631            cmd.arg(program);
 632        }
 633        cmd
 634    } else {
 635        Command::new(program)
 636    }
 637}
 638
 639fn run_editor<S: AsRef<OsStr>>(config: &Config, filename: S) -> Result<()> {
 640    let editor = try!(get_editor(&config));
 641    let editor_status = try!(cmd_maybe_shell(editor, true).arg(&filename).status());
 642    if !editor_status.success() {
 643        return Err(format!("Editor exited with status {}", editor_status).into());
 644    }
 645    Ok(())
 646}
 647
 648struct Output {
 649    pager: Option<std::process::Child>,
 650    include_stderr: bool,
 651}
 652
 653impl Output {
 654    fn new() -> Self {
 655        Output { pager: None, include_stderr: false }
 656    }
 657
 658    fn auto_pager(&mut self, config: &Config, for_cmd: &str, default: bool) -> Result<()> {
 659        if let Some(pager) = get_pager(config, for_cmd, default) {
 660            let mut cmd = cmd_maybe_shell(pager, false);
 661            cmd.stdin(std::process::Stdio::piped());
 662            if env::var_os("LESS").is_none() {
 663                cmd.env("LESS", "FRX");
 664            }
 665            if env::var_os("LV").is_none() {
 666                cmd.env("LV", "-c");
 667            }
 668            let child = try!(cmd.spawn());
 669            self.pager = Some(child);
 670            self.include_stderr = isatty::stderr_isatty();
 671        }
 672        Ok(())
 673    }
 674
 675    // Get a color to write text with, taking git configuration into account.
 676    //
 677    // config: the configuration to determine the color from.
 678    // command: the git command to act like.
 679    // slot: the color "slot" of that git command to act like.
 680    // default: the color to use if not configured.
 681    fn get_color(&self, config: &Config, command: &str, slot: &str, default: &str) -> Result<Style> {
 682        if !cfg!(unix) {
 683            return Ok(Style::new());
 684        }
 685        let color_ui = try!(notfound_to_none(config.get_str("color.ui"))).unwrap_or("auto");
 686        let color_cmd = try!(notfound_to_none(config.get_str(&format!("color.{}", command)))).unwrap_or(color_ui);
 687        if color_cmd == "never" || Config::parse_bool(color_cmd) == Ok(false) {
 688            return Ok(Style::new());
 689        }
 690        if self.pager.is_some() {
 691            let color_pager = try!(notfound_to_none(config.get_bool(&format!("color.pager")))).unwrap_or(true);
 692            if !color_pager {
 693                return Ok(Style::new());
 694            }
 695        } else if !isatty::stdout_isatty() {
 696            return Ok(Style::new());
 697        }
 698        let cfg = format!("color.{}.{}", command, slot);
 699        let color = try!(notfound_to_none(config.get_str(&cfg))).unwrap_or(default);
 700        colorparse::parse(color).map_err(|e| format!("Error parsing {}: {}", cfg, e).into())
 701   }
 702
 703    fn write_err(&mut self, msg: &str) {
 704        if self.include_stderr {
 705            if write!(self, "{}", msg).is_err() {
 706                write!(std::io::stderr(), "{}", msg).unwrap();
 707            }
 708        } else {
 709            write!(std::io::stderr(), "{}", msg).unwrap();
 710        }
 711    }
 712}
 713
 714impl Drop for Output {
 715    fn drop(&mut self) {
 716        if let Some(ref mut child) = self.pager {
 717            let status = child.wait().unwrap();
 718            if !status.success() {
 719                writeln!(std::io::stderr(), "Pager exited with status {}", status).unwrap();
 720            }
 721        }
 722    }
 723}
 724
 725impl IoWrite for Output {
 726    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
 727        match self.pager {
 728            Some(ref mut child) => child.stdin.as_mut().unwrap().write(buf),
 729            None => std::io::stdout().write(buf),
 730        }
 731    }
 732
 733    fn flush(&mut self) -> std::io::Result<()> {
 734        match self.pager {
 735            Some(ref mut child) => child.stdin.as_mut().unwrap().flush(),
 736            None => std::io::stdout().flush(),
 737        }
 738    }
 739}
 740
 741fn get_signature(config: &Config, which: &str) -> Result<git2::Signature<'static>> {
 742    let name_var = ["GIT_", which, "_NAME"].concat();
 743    let email_var = ["GIT_", which, "_EMAIL"].concat();
 744    let which_lc = which.to_lowercase();
 745    let name = try!(env::var(&name_var).or_else(
 746            |_| config.get_string("user.name").or_else(
 747                |_| Err(format!("Could not determine {} name: checked ${} and user.name in git config", which_lc, name_var)))));
 748    let email = try!(env::var(&email_var).or_else(
 749            |_| config.get_string("user.email").or_else(
 750                |_| env::var("EMAIL").or_else(
 751                    |_| Err(format!("Could not determine {} email: checked ${}, user.email in git config, and $EMAIL", which_lc, email_var))))));
 752    Ok(try!(git2::Signature::now(&name, &email)))
 753}
 754
 755fn commit_status(out: &mut Output, repo: &Repository, m: &ArgMatches, do_status: bool) -> Result<()> {
 756    let config = try!(try!(repo.config()).snapshot());
 757    let shead = match repo.find_reference(SHEAD_REF) {
 758        Err(ref e) if e.code() == git2::ErrorCode::NotFound => { println!("No series; use \"git series start <name>\" to start"); return Ok(()); }
 759        result => try!(result),
 760    };
 761    let series_name = try!(shead_series_name(&shead));
 762
 763    if do_status {
 764        try!(out.auto_pager(&config, "status", false));
 765    }
 766    let get_color = |out: &Output, color: &str, default: &str| {
 767        if do_status {
 768            out.get_color(&config, "status", color, default)
 769        } else {
 770            Ok(Style::new())
 771        }
 772    };
 773    let color_normal = Style::new();
 774    let color_header = try!(get_color(out, "header", "normal"));
 775    let color_updated = try!(get_color(out, "updated", "green"));
 776    let color_changed = try!(get_color(out, "changed", "red"));
 777
 778    let write_status = |status: &mut Vec<ansi_term::ANSIString>, diff: &Diff, heading: &str, color: &Style, show_hints: bool, hints: &[&str]| -> Result<bool> {
 779        let mut changes = false;
 780
 781        try!(diff.foreach(&mut |delta, _| {
 782            if !changes {
 783                changes = true;
 784                status.push(color_header.paint(format!("{}\n", heading.to_string())));
 785                if show_hints {
 786                    for hint in hints {
 787                        status.push(color_header.paint(format!("  ({})\n", hint)));
 788                    }
 789                }
 790                status.push(color_normal.paint("\n"));
 791            }
 792            status.push(color_normal.paint("        "));
 793            status.push(color.paint(format!("{:?}:   {}\n", delta.status(), delta.old_file().path().unwrap().to_str().unwrap())));
 794            true
 795        }, None, None, None));
 796
 797        if changes {
 798            status.push(color_normal.paint("\n"));
 799        }
 800
 801        Ok(changes)
 802    };
 803
 804    let mut status = Vec::new();
 805    status.push(color_header.paint(format!("On series {}\n", series_name)));
 806
 807    let mut internals = try!(Internals::read(repo));
 808    let working_tree = try!(repo.find_tree(try!(internals.working.write())));
 809    let staged_tree = try!(repo.find_tree(try!(internals.staged.write())));
 810
 811    let shead_commit = match shead.resolve() {
 812        Ok(r) => Some(try!(peel_to_commit(r))),
 813        Err(ref e) if e.code() == git2::ErrorCode::NotFound => {
 814            status.push(color_header.paint("\nInitial series commit\n"));
 815            None
 816        }
 817        Err(e) => try!(Err(e)),
 818    };
 819    let shead_tree = match shead_commit {
 820        Some(ref c) => Some(try!(c.tree())),
 821        None => None,
 822    };
 823
 824    let commit_all = m.is_present("all");
 825
 826    let (changes, tree, diff) = if commit_all {
 827        let diff = try!(repo.diff_tree_to_tree(shead_tree.as_ref(), Some(&working_tree), None));
 828        let changes = try!(write_status(&mut status, &diff, "Changes to be committed:", &color_normal, false, &[]));
 829        if !changes {
 830            status.push(color_normal.paint("nothing to commit; series unchanged\n"));
 831        }
 832        (changes, working_tree, diff)
 833    } else {
 834        let diff = try!(repo.diff_tree_to_tree(shead_tree.as_ref(), Some(&staged_tree), None));
 835        let changes_to_be_committed = try!(write_status(&mut status, &diff,
 836                "Changes to be committed:", &color_updated, do_status,
 837                &["use \"git series commit\" to commit",
 838                  "use \"git series unadd <file>...\" to undo add"]));
 839
 840        let diff_not_staged = try!(repo.diff_tree_to_tree(Some(&staged_tree), Some(&working_tree), None));
 841        let changes_not_staged = try!(write_status(&mut status, &diff_not_staged,
 842                "Changes not staged for commit:", &color_changed, do_status,
 843                &["use \"git series add <file>...\" to update what will be committed"]));
 844
 845        if !changes_to_be_committed {
 846            if changes_not_staged {
 847                status.push(color_normal.paint("no changes added to commit (use \"git series add\" or \"git series commit -a\")\n"));
 848            } else {
 849                status.push(color_normal.paint("nothing to commit; series unchanged\n"));
 850            }
 851        }
 852
 853        (changes_to_be_committed, staged_tree, diff)
 854    };
 855
 856    let status = ansi_term::ANSIStrings(&status).to_string();
 857    if do_status || !changes {
 858        if do_status {
 859            try!(write!(out, "{}", status));
 860        } else {
 861            return Err(status.into());
 862        }
 863        return Ok(());
 864    }
 865
 866    // Check that the commit includes the series
 867    let series_id = match tree.get_name("series") {
 868        None => { return Err(concat!("Cannot commit: initial commit must include \"series\"\n",
 869                                     "Use \"git series add series\" or \"git series commit -a\"").into()); }
 870        Some(series) => series.id()
 871    };
 872
 873    // Check that the base is still an ancestor of the series
 874    if let Some(base) = tree.get_name("base") {
 875        if base.id() != series_id && !try!(repo.graph_descendant_of(series_id, base.id())) {
 876            let (base_short_id, base_summary) = try!(commit_summarize_components(&repo, base.id()));
 877            let (series_short_id, series_summary) = try!(commit_summarize_components(&repo, series_id));
 878            return Err(format!(concat!(
 879                       "Cannot commit: base {} is not an ancestor of patch series {}\n",
 880                       "base   {} {}\n",
 881                       "series {} {}"),
 882                       base_short_id, series_short_id,
 883                       base_short_id, base_summary,
 884                       series_short_id, series_summary).into());
 885        }
 886    }
 887
 888    let msg = match m.value_of("m") {
 889        Some(s) => s.to_string(),
 890        None => {
 891            let filename = repo.path().join("SCOMMIT_EDITMSG");
 892            let mut file = try!(File::create(&filename));
 893            try!(write!(file, "{}", COMMIT_MESSAGE_COMMENT));
 894            for line in status.lines() {
 895                if line.is_empty() {
 896                    try!(writeln!(file, "#"));
 897                } else {
 898                    try!(writeln!(file, "# {}", line));
 899                }
 900            }
 901            if m.is_present("verbose") {
 902                try!(writeln!(file, "{}\n{}", SCISSOR_LINE, SCISSOR_COMMENT));
 903                try!(write_diff(&mut file, &DiffColors::plain(), &diff, false));
 904            }
 905            drop(file);
 906            try!(run_editor(&config, &filename));
 907            let mut file = try!(File::open(&filename));
 908            let mut msg = String::new();
 909            try!(file.read_to_string(&mut msg));
 910            if let Some(scissor_index) = msg.find(SCISSOR_LINE) {
 911                msg.truncate(scissor_index);
 912            }
 913            try!(git2::message_prettify(msg, git2::DEFAULT_COMMENT_CHAR))
 914        }
 915    };
 916    if msg.is_empty() {
 917        return Err("Aborting series commit due to empty commit message.".into());
 918    }
 919
 920    let author = try!(get_signature(&config, "AUTHOR"));
 921    let committer = try!(get_signature(&config, "COMMITTER"));
 922    let mut parents: Vec<Oid> = Vec::new();
 923    // Include all commits from tree, to keep them reachable and fetchable.
 924    for e in tree.iter() {
 925        if e.kind() == Some(ObjectType::Commit) && e.name().unwrap() != "base" {
 926            parents.push(e.id())
 927        }
 928    }
 929    let parents = try!(parents_from_ids(repo, parents));
 930    let parents_ref: Vec<&_> = shead_commit.iter().chain(parents.iter()).collect();
 931    let new_commit_oid = try!(repo.commit(Some(SHEAD_REF), &author, &committer, &msg, &tree, &parents_ref));
 932
 933    if commit_all {
 934        internals.staged = try!(repo.treebuilder(Some(&tree)));
 935        try!(internals.write(repo));
 936    }
 937
 938    let (new_commit_short_id, new_commit_summary) = try!(commit_summarize_components(&repo, new_commit_oid));
 939    try!(writeln!(out, "[{} {}] {}", series_name, new_commit_short_id, new_commit_summary));
 940
 941    Ok(())
 942}
 943
 944fn cover(repo: &Repository, m: &ArgMatches) -> Result<()> {
 945    let mut internals = try!(Internals::read(repo));
 946
 947    let (working_cover_id, working_cover_content) = match try!(internals.working.get("cover")) {
 948        None => (zero_oid(), String::new()),
 949        Some(entry) => (entry.id(), try!(std::str::from_utf8(try!(repo.find_blob(entry.id())).content())).to_string()),
 950    };
 951
 952    if m.is_present("delete") {
 953        if working_cover_id.is_zero() {
 954            return Err("No cover to delete".into());
 955        }
 956        try!(internals.working.remove("cover"));
 957        try!(internals.write(repo));
 958        println!("Deleted cover letter");
 959        return Ok(());
 960    }
 961
 962    let filename = repo.path().join("COVER_EDITMSG");
 963    let mut file = try!(File::create(&filename));
 964    if working_cover_content.is_empty() {
 965        try!(write!(file, "{}", COVER_LETTER_COMMENT));
 966    } else {
 967        try!(write!(file, "{}", working_cover_content));
 968    }
 969    drop(file);
 970    let config = try!(repo.config());
 971    try!(run_editor(&config, &filename));
 972    let mut file = try!(File::open(&filename));
 973    let mut msg = String::new();
 974    try!(file.read_to_string(&mut msg));
 975    let msg = try!(git2::message_prettify(msg, git2::DEFAULT_COMMENT_CHAR));
 976    if msg.is_empty() {
 977        return Err("Empty cover letter; not changing.\n(To delete the cover letter, use \"git series cover -d\".)".into());
 978    }
 979
 980    let new_cover_id = try!(repo.blob(msg.as_bytes()));
 981    if new_cover_id == working_cover_id {
 982        println!("Cover letter unchanged");
 983    } else {
 984        try!(internals.working.insert("cover", new_cover_id, GIT_FILEMODE_BLOB as i32));
 985        try!(internals.write(repo));
 986        println!("Updated cover letter");
 987    }
 988
 989    Ok(())
 990}
 991
 992fn cp_mv(repo: &Repository, m: &ArgMatches, mv: bool) -> Result<()> {
 993    let shead_target = if let Some(shead) = try!(notfound_to_none(repo.find_reference(SHEAD_REF))) {
 994        Some(try!(shead_series_name(&shead)))
 995    } else {
 996        None
 997    };
 998    let mut source_dest = m.values_of("source_dest").unwrap();
 999    let dest = source_dest.next_back().unwrap();
1000    let (update_shead, source) = match source_dest.next_back().map(String::from) {
1001        Some(name) => (shead_target.as_ref() == Some(&name), name),
1002        None => (true, try!(shead_target.ok_or("No current series"))),
1003    };
1004
1005    if try!(Internals::exists(&repo, dest)) {
1006        return Err(format!("The destination series \"{}\" already exists", dest).into());
1007    }
1008    if !try!(Internals::copy(&repo, &source, &dest)) {
1009        return Err(format!("The source series \"{}\" does not exist", source).into());
1010    }
1011
1012    if mv {
1013        if update_shead {
1014            let prefixed_dest = &[SERIES_PREFIX, dest].concat();
1015            try!(repo.reference_symbolic(SHEAD_REF, &prefixed_dest, true, &format!("git series mv {} {}", source, dest)));
1016        }
1017        try!(Internals::delete(&repo, &source));
1018    }
1019
1020    Ok(())
1021}
1022
1023fn date_822(t: git2::Time) -> String {
1024    let offset = chrono::offset::fixed::FixedOffset::east(t.offset_minutes()*60);
1025    let datetime = offset.timestamp(t.seconds(), 0);
1026    datetime.to_rfc2822()
1027}
1028
1029fn shortlog(commits: &mut [Commit]) -> String {
1030    let mut s = String::new();
1031    let mut author_map = std::collections::HashMap::new();
1032
1033    for mut commit in commits {
1034        let author = commit.author().name().unwrap().to_string();
1035        author_map.entry(author).or_insert(Vec::new()).push(commit.summary().unwrap().to_string());
1036    }
1037
1038    let mut authors: Vec<_> = author_map.keys().collect();
1039    authors.sort();
1040    let mut first = true;
1041    for author in authors {
1042        if first {
1043            first = false;
1044        } else {
1045            writeln!(s, "").unwrap();
1046        }
1047        let summaries = author_map.get(author).unwrap();
1048        writeln!(s, "{} ({}):", author, summaries.len()).unwrap();
1049        for summary in summaries {
1050            writeln!(s, "  {}", summary).unwrap();
1051        }
1052    }
1053
1054    s
1055}
1056
1057fn ascii_isalnum(c: char) -> bool {
1058    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
1059}
1060
1061fn sanitize_summary(summary: &str) -> String {
1062    let mut s = String::with_capacity(summary.len());
1063    let mut prev_dot = false;
1064    let mut need_space = false;
1065    for c in summary.chars() {
1066        if ascii_isalnum(c) || c == '_' || c == '.' {
1067            if need_space {
1068                s.push('-');
1069                need_space = false;
1070            }
1071            if !(prev_dot && c == '.') {
1072                s.push(c);
1073            }
1074        } else {
1075            if !s.is_empty() {
1076                need_space = true;
1077            }
1078        }
1079        prev_dot = c == '.';
1080    }
1081    let end = s.trim_right_matches(|c| c == '.' || c == '-').len();
1082    s.truncate(end);
1083    s
1084}
1085
1086#[test]
1087fn test_sanitize_summary() {
1088    let tests = vec![
1089        ("", ""),
1090        ("!!!!!", ""),
1091        ("Test", "Test"),
1092        ("Test case", "Test-case"),
1093        ("Test    case", "Test-case"),
1094        ("    Test    case    ", "Test-case"),
1095        ("...Test...case...", ".Test.case"),
1096        ("...Test...case.!!", ".Test.case"),
1097        (".!.Test.!.case.!.", ".-.Test.-.case"),
1098    ];
1099    for (summary, sanitized) in tests {
1100        assert_eq!(sanitize_summary(summary), sanitized.to_string());
1101    }
1102}
1103
1104fn split_message(message: &str) -> (&str, &str) {
1105    let mut iter = message.splitn(2, '\n');
1106    let subject = iter.next().unwrap().trim_right();
1107    let body = iter.next().map(|s| s.trim_left()).unwrap_or("");
1108    (subject, body)
1109}
1110
1111struct DiffColors {
1112    commit: Style,
1113    meta: Style,
1114    frag: Style,
1115    func: Style,
1116    context: Style,
1117    old: Style,
1118    new: Style,
1119    series_old: Style,
1120    series_new: Style,
1121}
1122
1123impl DiffColors {
1124    fn plain() -> Self {
1125        DiffColors {
1126            commit: Style::new(),
1127            meta: Style::new(),
1128            frag: Style::new(),
1129            func: Style::new(),
1130            context: Style::new(),
1131            old: Style::new(),
1132            new: Style::new(),
1133            series_old: Style::new(),
1134            series_new: Style::new(),
1135        }
1136    }
1137
1138    fn new(out: &Output, config: &Config) -> Result<Self> {
1139        let old = try!(out.get_color(&config, "diff", "old", "red"));
1140        let new = try!(out.get_color(&config, "diff", "new", "green"));
1141        Ok(DiffColors {
1142            commit: try!(out.get_color(&config, "diff", "commit", "yellow")),
1143            meta: try!(out.get_color(&config, "diff", "meta", "bold")),
1144            frag: try!(out.get_color(&config, "diff", "frag", "cyan")),
1145            func: try!(out.get_color(&config, "diff", "func", "normal")),
1146            context: try!(out.get_color(&config, "diff", "context", "normal")),
1147            old: old,
1148            new: new,
1149            series_old: old.reverse(),
1150            series_new: new.reverse(),
1151        })
1152    }
1153}
1154
1155fn diffstat(diff: &Diff) -> Result<String> {
1156    let stats = try!(diff.stats());
1157    let stats_buf = try!(stats.to_buf(git2::DIFF_STATS_FULL|git2::DIFF_STATS_INCLUDE_SUMMARY, 72));
1158    Ok(stats_buf.as_str().unwrap().to_string())
1159}
1160
1161fn write_diff<W: IoWrite>(f: &mut W, colors: &DiffColors, diff: &Diff, simplify: bool) -> Result<usize> {
1162    let mut err = Ok(());
1163    let mut lines = 0;
1164    let normal = Style::new();
1165    try!(diff.print(git2::DiffFormat::Patch, |_, _, l| {
1166        err = || -> Result<()> {
1167            let o = l.origin();
1168            let style = match o {
1169                '-'|'<' => colors.old,
1170                '+'|'>' => colors.new,
1171                _ if simplify => normal,
1172                ' '|'=' => colors.context,
1173                'F' => colors.meta,
1174                'H' => colors.frag,
1175                _ => normal,
1176            };
1177            let obyte = [o as u8];
1178            let mut v = Vec::new();
1179            if o == '+' || o == '-' || o == ' ' {
1180                v.push(style.paint(&obyte[..]));
1181            }
1182            if simplify {
1183                if o == 'H' {
1184                    v.push(normal.paint("@@\n".as_bytes()));
1185                    lines += 1;
1186                } else if o == 'F' {
1187                    for line in l.content().split(|c| *c == b'\n') {
1188                        if !line.is_empty() && !line.starts_with(b"diff --git") && !line.starts_with(b"index ") {
1189                            v.push(normal.paint(line.to_owned()));
1190                            v.push(normal.paint("\n".as_bytes()));
1191                            lines += 1;
1192                        }
1193                    }
1194                } else {
1195                    v.push(style.paint(l.content()));
1196                    lines += 1;
1197                }
1198            } else if o == 'H' {
1199                // Split frag and func
1200                let line = l.content();
1201                let at = &|&(_,&c): &(usize, &u8)| c == b'@';
1202                let not_at = &|&(_,&c): &(usize, &u8)| c != b'@';
1203                match line.iter().enumerate().skip_while(at).skip_while(not_at).skip_while(at).nth(1).unwrap_or((0,&b'\n')) {
1204                    (_,&c) if c == b'\n' => v.push(style.paint(&line[..line.len()-1])),
1205                    (pos,_) => {
1206                        v.push(style.paint(&line[..pos-1]));
1207                        v.push(normal.paint(" ".as_bytes()));
1208                        v.push(colors.func.paint(&line[pos..line.len()-1]));
1209                    },
1210                }
1211                v.push(normal.paint("\n".as_bytes()));
1212            } else {
1213                // The less pager resets ANSI colors at each newline, so emit colors separately for
1214                // each line.
1215                for (n, line) in l.content().split(|c| *c == b'\n').enumerate() {
1216                    if n != 0 {
1217                        v.push(normal.paint("\n".as_bytes()));
1218                    }
1219                    if !line.is_empty() {
1220                        v.push(style.paint(line));
1221                    }
1222                }
1223            }
1224            try!(ansi_term::ANSIByteStrings(&v).write_to(f));
1225            Ok(())
1226        }();
1227        err.is_ok()
1228    }));
1229    try!(err);
1230    Ok(lines)
1231}
1232
1233fn get_commits(repo: &Repository, base: Oid, series: Oid) -> Result<Vec<Commit>> {
1234    let mut revwalk = try!(repo.revwalk());
1235    revwalk.set_sorting(git2::SORT_TOPOLOGICAL|git2::SORT_REVERSE);
1236    try!(revwalk.push(series));
1237    try!(revwalk.hide(base));
1238    revwalk.map(|c| {
1239        let id = try!(c);
1240        let commit = try!(repo.find_commit(id));
1241        Ok(commit)
1242    }).collect()
1243}
1244
1245fn write_series_diff<W: IoWrite>(out: &mut W, repo: &Repository, colors: &DiffColors, (base1, series1): (Oid, Oid), (base2, series2): (Oid, Oid)) -> Result<()> {
1246    let mut commits1 = try!(get_commits(repo, base1, series1));
1247    let mut commits2 = try!(get_commits(repo, base2, series2));
1248    for commit in commits1.iter().chain(commits2.iter()) {
1249        if commit.parent_ids().count() > 1 {
1250            try!(writeln!(out, "(Diffs of series with merge commits ({}) not yet supported)", commit.id()));
1251            return Ok(());
1252        }
1253    }
1254    let ncommon = commits1.iter().zip(commits2.iter()).take_while(|&(ref c1, ref c2)| c1.id() == c2.id()).count();
1255    drop(commits1.drain(..ncommon));
1256    drop(commits2.drain(..ncommon));
1257    let ncommits1 = commits1.len();
1258    let ncommits2 = commits2.len();
1259    let n = ncommits1 + ncommits2;
1260    if n == 0 {
1261        return Ok(());
1262    }
1263    let commit_text = &|commit: &Commit| {
1264        let parent = try!(commit.parent(0));
1265        let author = commit.author();
1266        let diff = try!(repo.diff_tree_to_tree(Some(&parent.tree().unwrap()), Some(&commit.tree().unwrap()), None));
1267        let mut v = Vec::new();
1268        try!(v.write_all(b"From: "));
1269        try!(v.write_all(author.name_bytes()));
1270        try!(v.write_all(b" <"));
1271        try!(v.write_all(author.email_bytes()));
1272        try!(v.write_all(b">\n\n"));
1273        try!(v.write_all(commit.message_bytes()));
1274        try!(v.write_all(b"\n"));
1275        let lines = try!(write_diff(&mut v, colors, &diff, true));
1276        Ok((v, lines))
1277    };
1278    let texts1: Vec<_> = try!(commits1.iter().map(commit_text).collect::<Result<_>>());
1279    let texts2: Vec<_> = try!(commits2.iter().map(commit_text).collect::<Result<_>>());
1280
1281    let mut weights = Vec::with_capacity(n*n);
1282    for i1 in 0..ncommits1 {
1283        for i2 in 0..ncommits2 {
1284            let patch = try!(git2::Patch::from_buffers(&texts1[i1].0, None, &texts2[i2].0, None, None));
1285            let (_, additions, deletions) = try!(patch.line_stats());
1286            weights.push(additions+deletions);
1287        }
1288        let w = texts1[i1].1 / 2;
1289        for _ in ncommits2..n {
1290            weights.push(w);
1291        }
1292    }
1293    for _ in ncommits1..n {
1294        for i2 in 0..ncommits2 {
1295            weights.push(texts2[i2].1 / 2);
1296        }
1297        for _ in ncommits2..n {
1298            weights.push(0);
1299        }
1300    }
1301    let mut weight_matrix = munkres::WeightMatrix::from_row_vec(n, weights);
1302    let result = munkres::solve_assignment(&mut weight_matrix);
1303
1304    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
1305    enum CommitState { Unhandled, Handled, Deleted };
1306    let mut commits2_from1: Vec<_> = std::iter::repeat(None).take(ncommits2).collect();
1307    let mut commits1_state: Vec<_> = std::iter::repeat(CommitState::Unhandled).take(ncommits1).collect();
1308    let mut commit_pairs = Vec::with_capacity(n);
1309    for (i1, i2) in result {
1310        if i1 < ncommits1 {
1311            if i2 < ncommits2 {
1312                commits2_from1[i2] = Some(i1);
1313            } else {
1314                commits1_state[i1] = CommitState::Deleted;
1315            }
1316        }
1317    }
1318
1319    // Show matching or new commits sorted by the new commit order. Show deleted commits after
1320    // showing all of their prerequisite commits.
1321    let mut commits1_state_index = 0;
1322    for (i2, opt_i1) in commits2_from1.iter().enumerate() {
1323        //for i1 in commits1_state_index..ncommits1 {
1324        while commits1_state_index < ncommits1 {
1325            match commits1_state[commits1_state_index] {
1326                CommitState::Unhandled => { break }
1327                CommitState::Handled => {},
1328                CommitState::Deleted => {
1329                    commit_pairs.push((Some(commits1_state_index), None));
1330                },
1331            }
1332            commits1_state_index += 1;
1333        }
1334        if let &Some(i1) = opt_i1 {
1335            commit_pairs.push((Some(i1), Some(i2)));
1336            commits1_state[i1] = CommitState::Handled;
1337        } else {
1338            commit_pairs.push((None, Some(i2)));
1339        }
1340    }
1341    for i1 in commits1_state_index..ncommits1 {
1342        if commits1_state[i1] == CommitState::Deleted {
1343            commit_pairs.push((Some(i1), None));
1344        }
1345    }
1346
1347    let normal = Style::new();
1348    let nl = |v: &mut Vec<_>| { v.push(normal.paint("\n".as_bytes())); };
1349    let mut v = Vec::new();
1350    v.push(colors.meta.paint("diff --series".as_bytes()));
1351    nl(&mut v);
1352
1353    let offset = ncommon + 1;
1354    let nwidth = max(ncommits1 + offset, ncommits2 + offset).to_string().len();
1355    let commits1_summaries: Vec<_> = try!(commits1.iter_mut().map(commit_obj_summarize_components).collect());
1356    let commits2_summaries: Vec<_> = try!(commits2.iter_mut().map(commit_obj_summarize_components).collect());
1357    let idwidth = commits1_summaries.iter().chain(commits2_summaries.iter()).map(|&(ref short_id, _)| short_id.len()).max().unwrap();
1358    for commit_pair in commit_pairs {
1359        match commit_pair {
1360            (None, None) => unreachable!(),
1361            (Some(i1), None) => {
1362                let (ref c1_short_id, ref c1_summary) = commits1_summaries[i1];
1363                v.push(colors.old.paint(format!("{:nwidth$}: {:idwidth$} < {:-<nwidth$}: {:-<idwidth$} {}",
1364                                                i1 + offset, c1_short_id, "", "", c1_summary, nwidth=nwidth, idwidth=idwidth).as_bytes().to_owned()));
1365                nl(&mut v);
1366            }
1367            (None, Some(i2)) => {
1368                let (ref c2_short_id, ref c2_summary) = commits2_summaries[i2];
1369                v.push(colors.new.paint(format!("{:-<nwidth$}: {:-<idwidth$} > {:nwidth$}: {:idwidth$} {}",
1370                                                "", "", i2 + offset, c2_short_id, c2_summary, nwidth=nwidth, idwidth=idwidth).as_bytes().to_owned()));
1371                nl(&mut v);
1372            }
1373            (Some(i1), Some(i2)) => {
1374                let mut patch = try!(git2::Patch::from_buffers(&texts1[i1].0, None, &texts2[i2].0, None, None));
1375                let (old, ch, new) = if let Delta::Unmodified = patch.delta().status() {
1376                    (colors.commit, '=', colors.commit)
1377                } else {
1378                    (colors.series_old, '!', colors.series_new)
1379                };
1380                let (ref c1_short_id, _) = commits1_summaries[i1];
1381                let (ref c2_short_id, ref c2_summary) = commits2_summaries[i2];
1382                v.push(old.paint(format!("{:nwidth$}: {:idwidth$}", i1 + offset, c1_short_id, nwidth=nwidth, idwidth=idwidth).as_bytes().to_owned()));
1383                v.push(colors.commit.paint(format!(" {} ", ch).as_bytes().to_owned()));
1384                v.push(new.paint(format!("{:nwidth$}: {:idwidth$}", i2 + offset, c2_short_id, nwidth=nwidth, idwidth=idwidth).as_bytes().to_owned()));
1385                v.push(colors.commit.paint(format!(" {}", c2_summary).as_bytes().to_owned()));
1386                nl(&mut v);
1387                try!(patch.print(&mut |_, _, l| {
1388                    let o = l.origin();
1389                    let style = match o {
1390                        '-'|'<' => old,
1391                        '+'|'>' => new,
1392                        _ => normal,
1393                    };
1394                    if o == '+' || o == '-' || o == ' ' {
1395                        v.push(style.paint(vec![o as u8]));
1396                    }
1397                    let style = if o == 'H' { colors.frag } else { normal };
1398                    if o != 'F' {
1399                        v.push(style.paint(l.content().to_owned()));
1400                    }
1401                    true
1402                }));
1403            }
1404        }
1405    }
1406
1407    try!(ansi_term::ANSIByteStrings(&v).write_to(out));
1408    Ok(())
1409}
1410
1411fn mail_signature() -> String {
1412    format!("-- \ngit-series {}", crate_version!())
1413}
1414
1415fn ensure_space(s: &str) -> &'static str {
1416    if s.is_empty() || s.ends_with(' ') {
1417        ""
1418    } else {
1419        " "
1420    }
1421}
1422
1423fn ensure_nl(s: &str) -> &'static str {
1424    if !s.ends_with('\n') {
1425        "\n"
1426    } else {
1427        ""
1428    }
1429}
1430
1431fn format(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
1432    let config = try!(try!(repo.config()).snapshot());
1433    let to_stdout = m.is_present("stdout");
1434    let no_from = m.is_present("no-from");
1435
1436    let shead_commit = try!(peel_to_commit(try!(try!(repo.find_reference(SHEAD_REF)).resolve())));
1437    let stree = try!(shead_commit.tree());
1438
1439    let series = try!(stree.get_name("series").ok_or("Internal error: series did not contain \"series\""));
1440    let base = try!(stree.get_name("base").ok_or("Cannot format series; no base set.\nUse \"git series base\" to set base."));
1441
1442    let mut revwalk = try!(repo.revwalk());
1443    revwalk.set_sorting(git2::SORT_TOPOLOGICAL|git2::SORT_REVERSE);
1444    try!(revwalk.push(series.id()));
1445    try!(revwalk.hide(base.id()));
1446    let mut commits: Vec<Commit> = try!(revwalk.map(|c| {
1447        let id = try!(c);
1448        let commit = try!(repo.find_commit(id));
1449        if commit.parent_ids().count() > 1 {
1450            return Err(format!("Error: cannot format merge commit as patch:\n{}", try!(commit_summarize(repo, id))).into());
1451        }
1452        Ok(commit)
1453    }).collect::<Result<_>>());
1454    if commits.is_empty() {
1455        return Err("No patches to format; series and base identical.".into());
1456    }
1457
1458    let committer = try!(get_signature(&config, "COMMITTER"));
1459    let committer_name = committer.name().unwrap();
1460    let committer_email = committer.email().unwrap();
1461    let message_id_suffix = format!("{}.git-series.{}", committer.when().seconds(), committer_email);
1462
1463    let cover_entry = stree.get_name("cover");
1464    let mut in_reply_to_message_id = m.value_of("in-reply-to").map(|v| {
1465        format!("{}{}{}",
1466                if v.starts_with('<') { "" } else { "<" },
1467                v,
1468                if v.ends_with('>') { "" } else { ">" })
1469    });
1470
1471    let version = m.value_of("reroll-count");
1472    let subject_prefix = if m.is_present("rfc") {
1473        "RFC PATCH"
1474    } else {
1475        m.value_of("subject-prefix").unwrap_or("PATCH")
1476    };
1477    let subject_patch = version.map_or(
1478            subject_prefix.to_string(),
1479            |n| format!("{}{}v{}", subject_prefix, ensure_space(&subject_prefix), n));
1480    let file_prefix = version.map_or("".to_string(), |n| format!("v{}-", n));
1481
1482    let signature = mail_signature();
1483
1484    if to_stdout {
1485        try!(out.auto_pager(&config, "format-patch", true));
1486    }
1487    let diffcolors = if to_stdout {
1488        try!(DiffColors::new(out, &config))
1489    } else {
1490        DiffColors::plain()
1491    };
1492    let mut out : Box<IoWrite> = if to_stdout {
1493        Box::new(out)
1494    } else {
1495        Box::new(std::io::stdout())
1496    };
1497    let patch_file = |name: &str| -> Result<Box<IoWrite>> {
1498        let name = format!("{}{}", file_prefix, name);
1499        println!("{}", name);
1500        Ok(Box::new(try!(File::create(name))))
1501    };
1502
1503    if let Some(ref entry) = cover_entry {
1504        let cover_blob = try!(repo.find_blob(entry.id()));
1505        let content = try!(std::str::from_utf8(cover_blob.content())).to_string();
1506        let (subject, body) = split_message(&content);
1507
1508        let series_tree = try!(repo.find_commit(series.id())).tree().unwrap();
1509        let base_tree = try!(repo.find_commit(base.id())).tree().unwrap();
1510        let diff = try!(repo.diff_tree_to_tree(Some(&base_tree), Some(&series_tree), None));
1511        let stats = try!(diffstat(&diff));
1512
1513        if !to_stdout {
1514            out = try!(patch_file("0000-cover-letter.patch"));
1515        }
1516        try!(writeln!(out, "From {} Mon Sep 17 00:00:00 2001", shead_commit.id()));
1517        let cover_message_id = format!("<cover.{}.{}>", shead_commit.id(), message_id_suffix);
1518        try!(writeln!(out, "Message-Id: {}", cover_message_id));
1519        if let Some(ref message_id) = in_reply_to_message_id {
1520            try!(writeln!(out, "In-Reply-To: {}", message_id));
1521            try!(writeln!(out, "References: {}", message_id));
1522        }
1523        in_reply_to_message_id = Some(cover_message_id);
1524        try!(writeln!(out, "From: {} <{}>", committer_name, committer_email));
1525        try!(writeln!(out, "Date: {}", date_822(committer.when())));
1526        try!(writeln!(out, "Subject: [{}{}0/{}] {}\n", subject_patch, ensure_space(&subject_patch), commits.len(), subject));
1527        if !body.is_empty() {
1528            try!(writeln!(out, "{}", body));
1529        }
1530        try!(writeln!(out, "{}", shortlog(&mut commits)));
1531        try!(writeln!(out, "{}", stats));
1532        try!(writeln!(out, "base-commit: {}", base.id()));
1533        try!(writeln!(out, "{}", signature));
1534    }
1535
1536    for (commit_num, commit) in commits.iter().enumerate() {
1537        let first_mail = commit_num == 0 && cover_entry.is_none();
1538        if to_stdout && !first_mail {
1539            try!(writeln!(out, ""));
1540        }
1541
1542        let message = commit.message().unwrap();
1543        let (subject, body) = split_message(message);
1544        let commit_id = commit.id();
1545        let commit_author = commit.author();
1546        let commit_author_name = commit_author.name().unwrap();
1547        let commit_author_email = commit_author.email().unwrap();
1548        let summary_sanitized = sanitize_summary(&subject);
1549        let this_message_id = format!("<{}.{}>", commit_id, message_id_suffix);
1550        let parent = try!(commit.parent(0));
1551        let diff = try!(repo.diff_tree_to_tree(Some(&parent.tree().unwrap()), Some(&commit.tree().unwrap()), None));
1552        let stats = try!(diffstat(&diff));
1553
1554        if !to_stdout {
1555            out = try!(patch_file(&format!("{:04}-{}.patch", commit_num+1, summary_sanitized)));
1556        }
1557        try!(writeln!(out, "From {} Mon Sep 17 00:00:00 2001", commit_id));
1558        try!(writeln!(out, "Message-Id: {}", this_message_id));
1559        if let Some(ref message_id) = in_reply_to_message_id {
1560            try!(writeln!(out, "In-Reply-To: {}", message_id));
1561            try!(writeln!(out, "References: {}", message_id));
1562        }
1563        if first_mail {
1564            in_reply_to_message_id = Some(this_message_id);
1565        }
1566        if no_from {
1567            try!(writeln!(out, "From: {} <{}>", commit_author_name, commit_author_email));
1568        } else {
1569            try!(writeln!(out, "From: {} <{}>", committer_name, committer_email));
1570        }
1571        try!(writeln!(out, "Date: {}", date_822(commit_author.when())));
1572        let prefix = if commits.len() == 1 && cover_entry.is_none() {
1573            if subject_patch.is_empty() {
1574                "".to_string()
1575            } else {
1576                format!("[{}] ", subject_patch)
1577            }
1578        } else {
1579            format!("[{}{}{}/{}] ", subject_patch, ensure_space(&subject_patch), commit_num+1, commits.len())
1580        };
1581        try!(writeln!(out, "Subject: {}{}\n", prefix, subject));
1582
1583        if !no_from && (commit_author_name != committer_name || commit_author_email != committer_email) {
1584            try!(writeln!(out, "From: {} <{}>\n", commit_author_name, commit_author_email));
1585        }
1586        if !body.is_empty() {
1587            try!(write!(out, "{}{}", body, ensure_nl(&body)));
1588        }
1589        try!(writeln!(out, "---"));
1590        try!(writeln!(out, "{}", stats));
1591        try!(write_diff(&mut out, &diffcolors, &diff, false));
1592        if first_mail {
1593            try!(writeln!(out, "\nbase-commit: {}", base.id()));
1594        }
1595        try!(writeln!(out, "{}", signature));
1596    }
1597
1598    Ok(())
1599}
1600
1601fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
1602    let config = try!(try!(repo.config()).snapshot());
1603    try!(out.auto_pager(&config, "log", true));
1604    let diffcolors = try!(DiffColors::new(out, &config));
1605
1606    let shead_id = try!(repo.refname_to_id(SHEAD_REF));
1607    let mut hidden_ids = std::collections::HashSet::new();
1608    let mut commit_stack = Vec::new();
1609    commit_stack.push(shead_id);
1610    while let Some(oid) = commit_stack.pop() {
1611        let commit = try!(repo.find_commit(oid));
1612        let tree = try!(commit.tree());
1613        for parent_id in commit.parent_ids() {
1614            if tree.get_id(parent_id).is_some() {
1615                hidden_ids.insert(parent_id);
1616            } else {
1617                commit_stack.push(parent_id);
1618            }
1619        }
1620    }
1621
1622    let mut revwalk = try!(repo.revwalk());
1623    revwalk.set_sorting(git2::SORT_TOPOLOGICAL);
1624    try!(revwalk.push(shead_id));
1625    for id in hidden_ids {
1626        try!(revwalk.hide(id));
1627    }
1628
1629    let show_diff = m.is_present("patch");
1630
1631    let mut first = true;
1632    for oid in revwalk {
1633        if first {
1634            first = false;
1635        } else {
1636            try!(writeln!(out, ""));
1637        }
1638        let oid = try!(oid);
1639        let commit = try!(repo.find_commit(oid));
1640        let author = commit.author();
1641
1642        try!(writeln!(out, "{}", diffcolors.commit.paint(format!("commit {}", oid))));
1643        try!(writeln!(out, "Author: {} <{}>", author.name().unwrap(), author.email().unwrap()));
1644        try!(writeln!(out, "Date:   {}\n", date_822(author.when())));
1645        for line in commit.message().unwrap().lines() {
1646            try!(writeln!(out, "    {}", line));
1647        }
1648
1649        if show_diff {
1650            let tree = try!(commit.tree());
1651            let parent_ids: Vec<_> = commit.parent_ids().take_while(|parent_id| tree.get_id(*parent_id).is_none()).collect();
1652
1653            try!(writeln!(out, ""));
1654            if parent_ids.len() > 1 {
1655                try!(writeln!(out, "(Diffs of series merge commits not yet supported)"));
1656            } else {
1657                let parent_tree = if parent_ids.len() == 0 {
1658                    None
1659                } else {
1660                    Some(try!(try!(repo.find_commit(parent_ids[0])).tree()))
1661                };
1662                let diff = try!(repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None));
1663                try!(write_diff(out, &diffcolors, &diff, false));
1664                if let Some(ptree) = parent_tree {
1665                    let series1 = try!(ptree.get_name("series").ok_or(format!("Could not find entry \"series\" in {}", ptree.id())));
1666                    let series2 = try!(tree.get_name("series").ok_or(format!("Could not find entry \"series\" in {}", tree.id())));
1667                    if let (Some(base1), Some(base2)) = (ptree.get_name("base"), tree.get_name("base")) {
1668                        try!(write_series_diff(out, repo, &diffcolors, (base1.id(), series1.id()), (base2.id(), series2.id())));
1669                    } else {
1670                        try!(writeln!(out, "(Can't diff series: base not found in both old and new versions.)"));
1671                    }
1672                }
1673            }
1674        }
1675    }
1676
1677    Ok(())
1678}
1679
1680fn rebase(repo: &Repository, m: &ArgMatches) -> Result<()> {
1681    match repo.state() {
1682        git2::RepositoryState::Clean => (),
1683        git2::RepositoryState::RebaseMerge if repo.path().join("rebase-merge").join("git-series").exists() => {
1684            return Err("git series rebase already in progress.\nUse \"git rebase --continue\" or \"git rebase --abort\".".into());
1685        },
1686        s => { return Err(format!("{:?} in progress; cannot rebase", s).into()); }
1687    }
1688
1689    let internals = try!(Internals::read(repo));
1690    let series = try!(try!(internals.working.get("series")).ok_or("Could not find entry \"series\" in working index"));
1691    let base = try!(try!(internals.working.get("base")).ok_or("Cannot rebase series; no base set.\nUse \"git series base\" to set base."));
1692    if series.id() == base.id() {
1693        return Err("No patches to rebase; series and base identical.".into());
1694    } else if !try!(repo.graph_descendant_of(series.id(), base.id())) {
1695        return Err(format!("Cannot rebase: current base {} not an ancestor of series {}", base.id(), series.id()).into());
1696    }
1697
1698    // Check for unstaged or uncommitted changes before attempting to rebase.
1699    let series_commit = try!(repo.find_commit(series.id()));
1700    let series_tree = try!(series_commit.tree());
1701    let mut unclean = String::new();
1702    if !diff_empty(&try!(repo.diff_tree_to_index(Some(&series_tree), None, None))) {
1703        writeln!(unclean, "Cannot rebase: you have unstaged changes.").unwrap();
1704    }
1705    if !diff_empty(&try!(repo.diff_index_to_workdir(None, None))) {
1706        if unclean.is_empty() {
1707            writeln!(unclean, "Cannot rebase: your index contains uncommitted changes.").unwrap();
1708        } else {
1709            writeln!(unclean, "Additionally, your index contains uncommitted changes.").unwrap();
1710        }
1711    }
1712    if !unclean.is_empty() {
1713        return Err(unclean.into());
1714    }
1715
1716    let mut revwalk = try!(repo.revwalk());
1717    revwalk.set_sorting(git2::SORT_TOPOLOGICAL|git2::SORT_REVERSE);
1718    try!(revwalk.push(series.id()));
1719    try!(revwalk.hide(base.id()));
1720    let commits: Vec<Commit> = try!(revwalk.map(|c| {
1721        let id = try!(c);
1722        let mut commit = try!(repo.find_commit(id));
1723        if commit.parent_ids().count() > 1 {
1724            return Err(format!("Error: cannot rebase merge commit:\n{}", try!(commit_obj_summarize(&mut commit))).into());
1725        }
1726        Ok(commit)
1727    }).collect::<Result<_>>());
1728
1729    let interactive = m.is_present("interactive");
1730    let onto = match m.value_of("onto") {
1731        None => None,
1732        Some(onto) => {
1733            let obj = try!(repo.revparse_single(onto));
1734            let commit = try!(obj.peel(ObjectType::Commit));
1735            Some(commit.id())
1736        },
1737    };
1738
1739    let newbase = onto.unwrap_or(base.id());
1740    if newbase == base.id() && !interactive {
1741        println!("Nothing to do: base unchanged and not rebasing interactively");
1742        return Ok(());
1743    }
1744
1745    let (base_short, _) = try!(commit_summarize_components(&repo, base.id()));
1746    let (newbase_short, _) = try!(commit_summarize_components(&repo, newbase));
1747    let (series_short, _) = try!(commit_summarize_components(&repo, series.id()));
1748
1749    let newbase_obj = try!(repo.find_commit(newbase)).into_object();
1750
1751    let dir = try!(TempDir::new_in(repo.path(), "rebase-merge"));
1752    let final_path = repo.path().join("rebase-merge");
1753    let mut create = std::fs::OpenOptions::new();
1754    create.write(true).create_new(true);
1755
1756    try!(create.open(dir.path().join("git-series")));
1757    try!(create.open(dir.path().join("quiet")));
1758    try!(create.open(dir.path().join("interactive")));
1759
1760    let mut head_name_file = try!(create.open(dir.path().join("head-name")));
1761    try!(writeln!(head_name_file, "detached HEAD"));
1762
1763    let mut onto_file = try!(create.open(dir.path().join("onto")));
1764    try!(writeln!(onto_file, "{}", newbase));
1765
1766    let mut orig_head_file = try!(create.open(dir.path().join("orig-head")));
1767    try!(writeln!(orig_head_file, "{}", series.id()));
1768
1769    let git_rebase_todo_filename = dir.path().join("git-rebase-todo");
1770    let mut git_rebase_todo = try!(create.open(&git_rebase_todo_filename));
1771    for mut commit in commits {
1772        try!(writeln!(git_rebase_todo, "pick {}", try!(commit_obj_summarize(&mut commit))));
1773    }
1774    if let Some(onto) = onto {
1775        try!(writeln!(git_rebase_todo, "exec git series base {}", onto));
1776    }
1777    try!(writeln!(git_rebase_todo, "\n# Rebase {}..{} onto {}", base_short, series_short, newbase_short));
1778    try!(write!(git_rebase_todo, "{}", REBASE_COMMENT));
1779    drop(git_rebase_todo);
1780
1781    // Interactive editor if interactive {
1782    if interactive {
1783        let config = try!(repo.config());
1784        try!(run_editor(&config, &git_rebase_todo_filename));
1785        let mut file = try!(File::open(&git_rebase_todo_filename));
1786        let mut todo = String::new();
1787        try!(file.read_to_string(&mut todo));
1788        let todo = try!(git2::message_prettify(todo, git2::DEFAULT_COMMENT_CHAR));
1789        if todo.is_empty() {
1790            return Err("Nothing to do".into());
1791        }
1792    }
1793
1794    // Avoid races by not calling .into_path until after the rename succeeds.
1795    try!(std::fs::rename(dir.path(), final_path));
1796    dir.into_path();
1797
1798    try!(checkout_tree(repo, &newbase_obj));
1799    try!(repo.reference("HEAD", newbase, true, &format!("rebase -i (start): checkout {}", newbase)));
1800
1801    let status = try!(Command::new("git").arg("rebase").arg("--continue").status());
1802    if !status.success() {
1803        return Err(format!("git rebase --continue exited with status {}", status).into());
1804    }
1805
1806    Ok(())
1807}
1808
1809fn req(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
1810    let config = try!(try!(repo.config()).snapshot());
1811    let shead = try!(repo.find_reference(SHEAD_REF));
1812    let shead_commit = try!(peel_to_commit(try!(shead.resolve())));
1813    let stree = try!(shead_commit.tree());
1814
1815    let series = try!(stree.get_name("series").ok_or("Internal error: series did not contain \"series\""));
1816    let series_id = series.id();
1817    let mut series_commit = try!(repo.find_commit(series_id));
1818    let base = try!(stree.get_name("base").ok_or("Cannot request pull; no base set.\nUse \"git series base\" to set base."));
1819    let mut base_commit = try!(repo.find_commit(base.id()));
1820
1821    let (cover_content, subject, cover_body) = if let Some(entry) = stree.get_name("cover") {
1822        let cover_blob = try!(repo.find_blob(entry.id()));
1823        let content = try!(std::str::from_utf8(cover_blob.content())).to_string();
1824        let (subject, body) = split_message(&content);
1825        (Some(content.to_string()), subject.to_string(), Some(body.to_string()))
1826    } else {
1827        (None, try!(shead_series_name(&shead)), None)
1828    };
1829
1830    let url = m.value_of("url").unwrap();
1831    let tag = m.value_of("tag").unwrap();
1832    let full_tag = format!("refs/tags/{}", tag);
1833    let full_tag_peeled = format!("{}^{{}}", full_tag);
1834    let full_head = format!("refs/heads/{}", tag);
1835    let mut remote = try!(repo.remote_anonymous(url));
1836    try!(remote.connect(git2::Direction::Fetch).map_err(|e| format!("Could not connect to remote repository {}\n{}", url, e)));
1837    let remote_heads = try!(remote.list());
1838
1839    /* Find the requested name as either a tag or head */
1840    let mut opt_remote_tag = None;
1841    let mut opt_remote_tag_peeled = None;
1842    let mut opt_remote_head = None;
1843    for h in remote_heads {
1844        if h.name() == full_tag {
1845            opt_remote_tag = Some(h.oid());
1846        } else if h.name() == full_tag_peeled {
1847            opt_remote_tag_peeled = Some(h.oid());
1848        } else if h.name() == full_head {
1849            opt_remote_head = Some(h.oid());
1850        }
1851    }
1852    let (msg, extra_body, remote_pull_name) = match (opt_remote_tag, opt_remote_tag_peeled, opt_remote_head) {
1853        (Some(remote_tag), Some(remote_tag_peeled), _) => {
1854            if remote_tag_peeled != series_id {
1855                return Err(format!("Remote tag {} does not refer to series {}", tag, series_id).into());
1856            }
1857            let local_tag = try!(repo.find_tag(remote_tag).map_err(|e|
1858                    format!("Could not find remote tag {} ({}) in local repository: {}", tag, remote_tag, e)));
1859            let mut local_tag_msg = local_tag.message().unwrap().to_string();
1860            if let Some(sig_index) = local_tag_msg.find("-----BEGIN PGP ") {
1861                local_tag_msg.truncate(sig_index);
1862            }
1863            let extra_body = match cover_content {
1864                Some(ref content) if !local_tag_msg.contains(content) => cover_body,
1865                _ => None,
1866            };
1867            (Some(local_tag_msg), extra_body, full_tag)
1868        },
1869        (Some(remote_tag), None, _) => {
1870            if remote_tag != series_id {
1871                return Err(format!("Remote unannotated tag {} does not refer to series {}", tag, series_id).into());
1872            }
1873            (cover_content, None, full_tag)
1874        }
1875        (_, _, Some(remote_head)) => {
1876            if remote_head != series_id {
1877                return Err(format!("Remote branch {} does not refer to series {}", tag, series_id).into());
1878            }
1879            (cover_content, None, full_head)
1880        },
1881        _ => {
1882            return Err(format!("Remote does not have either a tag or branch named {}", tag).into())
1883        }
1884    };
1885
1886    let commit_subject_date = |commit: &mut Commit| -> String {
1887        let date = date_822(commit.author().when());
1888        let summary = commit.summary().unwrap();
1889        format!("  {} ({})", summary, date)
1890    };
1891
1892    let mut revwalk = try!(repo.revwalk());
1893    revwalk.set_sorting(git2::SORT_TOPOLOGICAL|git2::SORT_REVERSE);
1894    try!(revwalk.push(series_id));
1895    try!(revwalk.hide(base.id()));
1896    let mut commits: Vec<Commit> = try!(revwalk.map(|c| {
1897        Ok(try!(repo.find_commit(try!(c))))
1898    }).collect::<Result<_>>());
1899    if commits.is_empty() {
1900        return Err("No patches to request pull of; series and base identical.".into());
1901    }
1902
1903    let author = try!(get_signature(&config, "AUTHOR"));
1904    let author_email = author.email().unwrap();
1905    let message_id = format!("<pull.{}.{}.git-series.{}>", shead_commit.id(), author.when().seconds(), author_email);
1906
1907    let diff = try!(repo.diff_tree_to_tree(Some(&base_commit.tree().unwrap()), Some(&series_commit.tree().unwrap()), None));
1908    let stats = try!(diffstat(&diff));
1909
1910    try!(out.auto_pager(&config, "request-pull", true));
1911    let diffcolors = try!(DiffColors::new(out, &config));
1912
1913    try!(writeln!(out, "From {} Mon Sep 17 00:00:00 2001", shead_commit.id()));
1914    try!(writeln!(out, "Message-Id: {}", message_id));
1915    try!(writeln!(out, "From: {} <{}>", author.name().unwrap(), author_email));
1916    try!(writeln!(out, "Date: {}", date_822(author.when())));
1917    try!(writeln!(out, "Subject: [GIT PULL] {}\n", subject));
1918    if let Some(extra_body) = extra_body {
1919        try!(writeln!(out, "{}", extra_body));
1920    }
1921    try!(writeln!(out, "The following changes since commit {}:\n", base.id()));
1922    try!(writeln!(out, "{}\n", commit_subject_date(&mut base_commit)));
1923    try!(writeln!(out, "are available in the git repository at:\n"));
1924    try!(writeln!(out, "  {} {}\n", url, remote_pull_name));
1925    try!(writeln!(out, "for you to fetch changes up to {}:\n", series.id()));
1926    try!(writeln!(out, "{}\n", commit_subject_date(&mut series_commit)));
1927    try!(writeln!(out, "----------------------------------------------------------------"));
1928    if let Some(msg) = msg {
1929        try!(writeln!(out, "{}", msg));
1930        try!(writeln!(out, "----------------------------------------------------------------"));
1931    }
1932    try!(writeln!(out, "{}", shortlog(&mut commits)));
1933    try!(writeln!(out, "{}", stats));
1934    if m.is_present("patch") {
1935        try!(write_diff(out, &diffcolors, &diff, false));
1936    }
1937    try!(writeln!(out, "{}", mail_signature()));
1938
1939    Ok(())
1940}
1941
1942fn main() {
1943    let m = App::new("git-series")
1944            .bin_name("git series")
1945            .about("Track patch series in git")
1946            .author("Josh Triplett <josh@joshtriplett.org>")
1947            .version(crate_version!())
1948            .global_setting(AppSettings::ColoredHelp)
1949            .global_setting(AppSettings::UnifiedHelpMessage)
1950            .global_setting(AppSettings::VersionlessSubcommands)
1951            .subcommands(vec![
1952                SubCommand::with_name("add")
1953                    .about("Add changes to the index for the next series commit")
1954                    .arg_from_usage("<change>... 'Changes to add (\"series\", \"base\", \"cover\")'"),
1955                SubCommand::with_name("base")
1956                    .about("Get or set the base commit for the patch series")
1957                    .arg(Arg::with_name("base").help("Base commit").conflicts_with("delete"))
1958                    .arg_from_usage("-d, --delete 'Clear patch series base'"),
1959                SubCommand::with_name("checkout")
1960                    .about("Resume work on a patch series; check out the current version")
1961                    .arg_from_usage("<name> 'Patch series to check out'"),
1962                SubCommand::with_name("commit")
1963                    .about("Record changes to the patch series")
1964                    .arg_from_usage("-a, --all 'Commit all changes'")
1965                    .arg_from_usage("-m [msg] 'Commit message'")
1966                    .arg_from_usage("-v, --verbose 'Show diff when preparing commit message'"),
1967                SubCommand::with_name("cover")
1968                    .about("Create or edit the cover letter for the patch series")
1969                    .arg_from_usage("-d, --delete 'Delete cover letter'"),
1970                SubCommand::with_name("cp")
1971                    .about("Copy a patch series")
1972                    .arg(Arg::with_name("source_dest").required(true).min_values(1).max_values(2).help("source (default: current series) and destination (required)")),
1973                SubCommand::with_name("delete")
1974                    .about("Delete a patch series")
1975                    .arg_from_usage("<name> 'Patch series to delete'"),
1976                SubCommand::with_name("detach")
1977                    .about("Stop working on any patch series"),
1978                SubCommand::with_name("diff")
1979                    .about("Show changes in the patch series"),
1980                SubCommand::with_name("format")
1981                    .about("Prepare patch series for email")
1982                    .arg_from_usage("--in-reply-to [Message-Id] 'Make the first mail a reply to the specified Message-Id'")
1983                    .arg_from_usage("--no-from 'Don't include in-body \"From:\" headers when formatting patches authored by others'")
1984                    .arg_from_usage("-v, --reroll-count=[N] 'Mark the patch series as PATCH vN'")
1985                    .arg(Arg::from_usage("--rfc 'Use [RFC PATCH] instead of the standard [PATCH] prefix'").conflicts_with("subject-prefix"))
1986                    .arg_from_usage("--stdout 'Write patches to stdout rather than files'")
1987                    .arg_from_usage("--subject-prefix [Subject-Prefix] 'Use [Subject-Prefix] instead of the standard [PATCH] prefix'"),
1988                SubCommand::with_name("log")
1989                    .about("Show the history of the patch series")
1990                    .arg_from_usage("-p, --patch 'Include a patch for each change committed to the series'"),
1991                SubCommand::with_name("mv")
1992                    .about("Move (rename) a patch series")
1993                    .visible_alias("rename")
1994                    .arg(Arg::with_name("source_dest").required(true).min_values(1).max_values(2).help("source (default: current series) and destination (required)")),
1995                SubCommand::with_name("rebase")
1996                    .about("Rebase the patch series")
1997                    .arg_from_usage("[onto] 'Commit to rebase onto'")
1998                    .arg_from_usage("-i, --interactive 'Interactively edit the list of commits'")
1999                    .group(ArgGroup::with_name("action").args(&["onto", "interactive"]).multiple(true).required(true)),
2000                SubCommand::with_name("req")
2001                    .about("Generate a mail requesting a pull of the patch series")
2002                    .visible_aliases(&["pull-request", "request-pull"])
2003                    .arg_from_usage("-p, --patch 'Include patch in the mail'")
2004                    .arg_from_usage("<url> 'Repository URL to request pull of'")
2005                    .arg_from_usage("<tag> 'Tag or branch name to request pull of'"),
2006                SubCommand::with_name("status")
2007                    .about("Show the status of the patch series"),
2008                SubCommand::with_name("start")
2009                    .about("Start a new patch series")
2010                    .arg_from_usage("<name> 'Patch series name'"),
2011                SubCommand::with_name("unadd")
2012                    .about("Undo \"git series add\", removing changes from the next series commit")
2013                    .arg_from_usage("<change>... 'Changes to remove (\"series\", \"base\", \"cover\")'"),
2014            ]).get_matches();
2015
2016    let mut out = Output::new();
2017
2018    let err = || -> Result<()> {
2019        let repo = try!(Repository::discover("."));
2020        match m.subcommand() {
2021            ("", _) => series(&mut out, &repo),
2022            ("add", Some(ref sm)) => add(&repo, &sm),
2023            ("base", Some(ref sm)) => base(&repo, &sm),
2024            ("checkout", Some(ref sm)) => checkout(&repo, &sm),
2025            ("commit", Some(ref sm)) => commit_status(&mut out, &repo, &sm, false),
2026            ("cover", Some(ref sm)) => cover(&repo, &sm),
2027            ("cp", Some(ref sm)) => cp_mv(&repo, &sm, false),
2028            ("delete", Some(ref sm)) => delete(&repo, &sm),
2029            ("detach", _) => detach(&repo),
2030            ("diff", _) => do_diff(&mut out, &repo),
2031            ("format", Some(ref sm)) => format(&mut out, &repo, &sm),
2032            ("log", Some(ref sm)) => log(&mut out, &repo, &sm),
2033            ("mv", Some(ref sm)) => cp_mv(&repo, &sm, true),
2034            ("rebase", Some(ref sm)) => rebase(&repo, &sm),
2035            ("req", Some(ref sm)) => req(&mut out, &repo, &sm),
2036            ("start", Some(ref sm)) => start(&repo, &sm),
2037            ("status", Some(ref sm)) => commit_status(&mut out, &repo, &sm, true),
2038            ("unadd", Some(ref sm)) => unadd(&repo, &sm),
2039            _ => unreachable!()
2040        }
2041    }();
2042
2043    if let Err(e) = err {
2044        let msg = e.to_string();
2045        out.write_err(&format!("{}{}", msg, ensure_nl(&msg)));
2046        drop(out);
2047        std::process::exit(1);
2048    }
2049}