main.rs

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