Skip to content

Commit f3b87ba

Browse files
authored
A first stab at a pull example. (#427)
A first stab at a pull example.
1 parent e8347d3 commit f3b87ba

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

examples/pull.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* libgit2 "pull" example - shows how to pull remote data into a local branch.
3+
*
4+
* Written by the libgit2 contributors
5+
*
6+
* To the extent possible under law, the author(s) have dedicated all copyright
7+
* and related and neighboring rights to this software to the public domain
8+
* worldwide. This software is distributed without any warranty.
9+
*
10+
* You should have received a copy of the CC0 Public Domain Dedication along
11+
* with this software. If not, see
12+
* <http://creativecommons.org/publicdomain/zero/1.0/>.
13+
*/
14+
15+
use git2::Repository;
16+
use std::io::{self, Write};
17+
use std::str;
18+
use structopt::StructOpt;
19+
20+
#[derive(StructOpt)]
21+
struct Args {
22+
arg_remote: Option<String>,
23+
arg_branch: Option<String>,
24+
}
25+
26+
fn do_fetch<'a>(
27+
repo: &'a git2::Repository,
28+
refs: &[&str],
29+
remote: &'a mut git2::Remote,
30+
) -> Result<git2::AnnotatedCommit<'a>, git2::Error> {
31+
let mut cb = git2::RemoteCallbacks::new();
32+
33+
// Print out our transfer progress.
34+
cb.transfer_progress(|stats| {
35+
if stats.received_objects() == stats.total_objects() {
36+
print!(
37+
"Resolving deltas {}/{}\r",
38+
stats.indexed_deltas(),
39+
stats.total_deltas()
40+
);
41+
} else if stats.total_objects() > 0 {
42+
print!(
43+
"Received {}/{} objects ({}) in {} bytes\r",
44+
stats.received_objects(),
45+
stats.total_objects(),
46+
stats.indexed_objects(),
47+
stats.received_bytes()
48+
);
49+
}
50+
io::stdout().flush().unwrap();
51+
true
52+
});
53+
54+
let mut fo = git2::FetchOptions::new();
55+
fo.remote_callbacks(cb);
56+
// Always fetch all tags.
57+
// Perform a download and also update tips
58+
fo.download_tags(git2::AutotagOption::All);
59+
println!("Fetching {} for repo", remote.name().unwrap());
60+
remote.fetch(refs, Some(&mut fo), None)?;
61+
62+
// If there are local objects (we got a thin pack), then tell the user
63+
// how many objects we saved from having to cross the network.
64+
let stats = remote.stats();
65+
if stats.local_objects() > 0 {
66+
println!(
67+
"\rReceived {}/{} objects in {} bytes (used {} local \
68+
objects)",
69+
stats.indexed_objects(),
70+
stats.total_objects(),
71+
stats.received_bytes(),
72+
stats.local_objects()
73+
);
74+
} else {
75+
println!(
76+
"\rReceived {}/{} objects in {} bytes",
77+
stats.indexed_objects(),
78+
stats.total_objects(),
79+
stats.received_bytes()
80+
);
81+
}
82+
83+
let fetch_head = repo.find_reference("FETCH_HEAD")?;
84+
Ok(repo.reference_to_annotated_commit(&fetch_head)?)
85+
}
86+
87+
fn fast_forward(
88+
repo: &Repository,
89+
lb: &mut git2::Reference,
90+
rc: &git2::AnnotatedCommit,
91+
) -> Result<(), git2::Error> {
92+
let name = match lb.name() {
93+
Some(s) => s.to_string(),
94+
None => String::from_utf8_lossy(lb.name_bytes()).to_string(),
95+
};
96+
let msg = format!("Fast-Forward: Setting {} to id: {}", name, rc.id());
97+
println!("{}", msg);
98+
lb.set_target(rc.id(), &msg)?;
99+
repo.set_head(&name)?;
100+
repo.checkout_head(Some(
101+
git2::build::CheckoutBuilder::default()
102+
// For some reason the force is required to make the working directory actually get updated
103+
// I suspect we should be adding some logic to handle dirty working directory states
104+
// but this is just an example so maybe not.
105+
.force(),
106+
))?;
107+
Ok(())
108+
}
109+
110+
fn normal_merge(
111+
repo: &Repository,
112+
local: &git2::AnnotatedCommit,
113+
remote: &git2::AnnotatedCommit,
114+
) -> Result<(), git2::Error> {
115+
let local_tree = repo.find_commit(local.id())?.tree()?;
116+
let remote_tree = repo.find_commit(remote.id())?.tree()?;
117+
let ancestor = repo
118+
.find_commit(repo.merge_base(local.id(), remote.id())?)?
119+
.tree()?;
120+
let mut idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
121+
122+
if idx.has_conflicts() {
123+
println!("Merge conficts detected...");
124+
repo.checkout_index(Some(&mut idx), None)?;
125+
return Ok(());
126+
}
127+
let result_tree = repo.find_tree(idx.write_tree_to(repo)?)?;
128+
// now create the merge commit
129+
let msg = format!("Merge: {} into {}", remote.id(), local.id());
130+
let sig = repo.signature()?;
131+
let local_commit = repo.find_commit(local.id())?;
132+
let remote_commit = repo.find_commit(remote.id())?;
133+
// Do our merge commit and set current branch head to that commit.
134+
let _merge_commit = repo.commit(
135+
Some("HEAD"),
136+
&sig,
137+
&sig,
138+
&msg,
139+
&result_tree,
140+
&[&local_commit, &remote_commit],
141+
)?;
142+
// Set working tree to match head.
143+
repo.checkout_head(None)?;
144+
Ok(())
145+
}
146+
147+
fn do_merge<'a>(
148+
repo: &'a Repository,
149+
remote_branch: &str,
150+
fetch_commit: git2::AnnotatedCommit<'a>,
151+
) -> Result<(), git2::Error> {
152+
// 1. do a merge analysis
153+
let analysis = repo.merge_analysis(&[&fetch_commit])?;
154+
155+
// 2. Do the appopriate merge
156+
if analysis.0.is_fast_forward() {
157+
println!("Doing a fast forward");
158+
// do a fast forward
159+
let refname = format!("refs/heads/{}", remote_branch);
160+
match repo.find_reference(&refname) {
161+
Ok(mut r) => {
162+
fast_forward(repo, &mut r, &fetch_commit)?;
163+
}
164+
Err(_) => {
165+
// The branch doesn't exist so just set the reference to the
166+
// commit directly. Usually this is because you are pulling
167+
// into an empty repository.
168+
repo.reference(
169+
&refname,
170+
fetch_commit.id(),
171+
true,
172+
&format!("Setting {} to {}", remote_branch, fetch_commit.id()),
173+
)?;
174+
repo.set_head(&refname)?;
175+
repo.checkout_head(Some(
176+
git2::build::CheckoutBuilder::default()
177+
.allow_conflicts(true)
178+
.conflict_style_merge(true)
179+
.force(),
180+
))?;
181+
}
182+
};
183+
} else if analysis.0.is_normal() {
184+
// do a normal merge
185+
let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?;
186+
normal_merge(&repo, &head_commit, &fetch_commit)?;
187+
} else {
188+
println!("Nothing to do...");
189+
}
190+
Ok(())
191+
}
192+
193+
fn run(args: &Args) -> Result<(), git2::Error> {
194+
let remote_name = args.arg_remote.as_ref().map(|s| &s[..]).unwrap_or("origin");
195+
let remote_branch = args.arg_branch.as_ref().map(|s| &s[..]).unwrap_or("master");
196+
let repo = Repository::open(".")?;
197+
let mut remote = repo.find_remote(remote_name)?;
198+
let fetch_commit = do_fetch(&repo, &[remote_branch], &mut remote)?;
199+
do_merge(&repo, &remote_branch, fetch_commit)
200+
}
201+
202+
fn main() {
203+
let args = Args::from_args();
204+
match run(&args) {
205+
Ok(()) => {}
206+
Err(e) => println!("error: {}", e),
207+
}
208+
}

0 commit comments

Comments
 (0)