@@ -111,8 +111,9 @@ pub fn push(
111
111
repo_path : & str ,
112
112
remote : & str ,
113
113
branch : & str ,
114
+ force : bool ,
114
115
basic_credential : Option < BasicAuthCredential > ,
115
- progress_sender : Sender < ProgressNotification > ,
116
+ progress_sender : Option < Sender < ProgressNotification > > ,
116
117
) -> Result < ( ) > {
117
118
scope_time ! ( "push" ) ;
118
119
@@ -122,15 +123,20 @@ pub fn push(
122
123
let mut options = PushOptions :: new ( ) ;
123
124
124
125
options. remote_callbacks ( remote_callbacks (
125
- Some ( progress_sender) ,
126
+ progress_sender,
126
127
basic_credential,
127
128
) ) ;
128
129
options. packbuilder_parallelism ( 0 ) ;
129
130
130
131
let branch_name = format ! ( "refs/heads/{}" , branch) ;
131
-
132
- remote. push ( & [ branch_name. as_str ( ) ] , Some ( & mut options) ) ?;
133
-
132
+ if force {
133
+ remote. push (
134
+ & [ String :: from ( "+" ) + & branch_name] ,
135
+ Some ( & mut options) ,
136
+ ) ?;
137
+ } else {
138
+ remote. push ( & [ branch_name. as_str ( ) ] , Some ( & mut options) ) ?;
139
+ }
134
140
branch_set_upstream ( & repo, branch) ?;
135
141
136
142
Ok ( ( ) )
@@ -306,4 +312,237 @@ mod tests {
306
312
. unwrap ( ) ;
307
313
assert_eq ! ( first, String :: from( "origin" ) ) ;
308
314
}
315
+
316
+ #[ test]
317
+ fn test_force_push ( ) {
318
+ use super :: push;
319
+ use std:: fs:: File ;
320
+ use std:: io:: Write ;
321
+
322
+ use crate :: sync:: commit:: commit;
323
+ use crate :: sync:: tests:: { repo_init, repo_init_bare} ;
324
+
325
+ // This test mimics the scenario of 2 people having 2
326
+ // local branches and both modifying the same file then
327
+ // both pushing, sequentially
328
+
329
+ let ( tmp_repo_dir, repo) = repo_init ( ) . unwrap ( ) ;
330
+ let ( tmp_other_repo_dir, other_repo) = repo_init ( ) . unwrap ( ) ;
331
+ let ( tmp_upstream_dir, _) = repo_init_bare ( ) . unwrap ( ) ;
332
+
333
+ repo. remote (
334
+ "origin" ,
335
+ tmp_upstream_dir. path ( ) . to_str ( ) . unwrap ( ) ,
336
+ )
337
+ . unwrap ( ) ;
338
+
339
+ other_repo
340
+ . remote (
341
+ "origin" ,
342
+ tmp_upstream_dir. path ( ) . to_str ( ) . unwrap ( ) ,
343
+ )
344
+ . unwrap ( ) ;
345
+
346
+ let tmp_repo_file_path =
347
+ tmp_repo_dir. path ( ) . join ( "temp_file.txt" ) ;
348
+ let mut tmp_repo_file =
349
+ File :: create ( tmp_repo_file_path) . unwrap ( ) ;
350
+ writeln ! ( tmp_repo_file, "TempSomething" ) . unwrap ( ) ;
351
+
352
+ commit (
353
+ tmp_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
354
+ "repo_1_commit" ,
355
+ )
356
+ . unwrap ( ) ;
357
+
358
+ push (
359
+ tmp_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
360
+ "origin" ,
361
+ "master" ,
362
+ false ,
363
+ None ,
364
+ None ,
365
+ )
366
+ . unwrap ( ) ;
367
+
368
+ let tmp_other_repo_file_path =
369
+ tmp_other_repo_dir. path ( ) . join ( "temp_file.txt" ) ;
370
+ let mut tmp_other_repo_file =
371
+ File :: create ( tmp_other_repo_file_path) . unwrap ( ) ;
372
+ writeln ! ( tmp_other_repo_file, "TempElse" ) . unwrap ( ) ;
373
+
374
+ commit (
375
+ tmp_other_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
376
+ "repo_2_commit" ,
377
+ )
378
+ . unwrap ( ) ;
379
+
380
+ // Attempt a normal push,
381
+ // should fail as branches diverged
382
+ assert_eq ! (
383
+ push(
384
+ tmp_other_repo_dir. path( ) . to_str( ) . unwrap( ) ,
385
+ "origin" ,
386
+ "master" ,
387
+ false ,
388
+ None ,
389
+ None ,
390
+ )
391
+ . is_err( ) ,
392
+ true
393
+ ) ;
394
+
395
+ // Attempt force push,
396
+ // should work as it forces the push through
397
+ assert_eq ! (
398
+ push(
399
+ tmp_other_repo_dir. path( ) . to_str( ) . unwrap( ) ,
400
+ "origin" ,
401
+ "master" ,
402
+ true ,
403
+ None ,
404
+ None ,
405
+ )
406
+ . is_err( ) ,
407
+ false
408
+ ) ;
409
+ }
410
+
411
+ #[ test]
412
+ fn test_force_push_rewrites_history ( ) {
413
+ use super :: push;
414
+ use std:: fs:: File ;
415
+ use std:: io:: Write ;
416
+
417
+ use crate :: sync:: commit:: commit;
418
+ use crate :: sync:: tests:: { repo_init, repo_init_bare} ;
419
+ use crate :: sync:: LogWalker ;
420
+
421
+ // This test mimics the scenario of 2 people having 2
422
+ // local branches and both modifying the same file then
423
+ // both pushing, sequentially
424
+
425
+ let ( tmp_repo_dir, repo) = repo_init ( ) . unwrap ( ) ;
426
+ let ( tmp_other_repo_dir, other_repo) = repo_init ( ) . unwrap ( ) ;
427
+ let ( tmp_upstream_dir, upstream) = repo_init_bare ( ) . unwrap ( ) ;
428
+
429
+ repo. remote (
430
+ "origin" ,
431
+ tmp_upstream_dir. path ( ) . to_str ( ) . unwrap ( ) ,
432
+ )
433
+ . unwrap ( ) ;
434
+
435
+ other_repo
436
+ . remote (
437
+ "origin" ,
438
+ tmp_upstream_dir. path ( ) . to_str ( ) . unwrap ( ) ,
439
+ )
440
+ . unwrap ( ) ;
441
+
442
+ let tmp_repo_file_path =
443
+ tmp_repo_dir. path ( ) . join ( "temp_file.txt" ) ;
444
+ let mut tmp_repo_file =
445
+ File :: create ( tmp_repo_file_path) . unwrap ( ) ;
446
+ writeln ! ( tmp_repo_file, "TempSomething" ) . unwrap ( ) ;
447
+
448
+ commit (
449
+ tmp_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
450
+ "repo_1_commit" ,
451
+ )
452
+ . unwrap ( ) ;
453
+
454
+ let mut repo_commit_ids = Vec :: < CommitId > :: new ( ) ;
455
+ LogWalker :: new ( & repo) . read ( & mut repo_commit_ids, 1 ) . unwrap ( ) ;
456
+
457
+ push (
458
+ tmp_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
459
+ "origin" ,
460
+ "master" ,
461
+ false ,
462
+ None ,
463
+ None ,
464
+ )
465
+ . unwrap ( ) ;
466
+
467
+ let upstream_parent = upstream
468
+ . find_commit ( ( repo_commit_ids[ 0 ] ) . into ( ) )
469
+ . unwrap ( )
470
+ . parents ( )
471
+ . next ( )
472
+ . unwrap ( )
473
+ . id ( ) ;
474
+
475
+ let tmp_other_repo_file_path =
476
+ tmp_other_repo_dir. path ( ) . join ( "temp_file.txt" ) ;
477
+ let mut tmp_other_repo_file =
478
+ File :: create ( tmp_other_repo_file_path) . unwrap ( ) ;
479
+ writeln ! ( tmp_other_repo_file, "TempElse" ) . unwrap ( ) ;
480
+
481
+ commit (
482
+ tmp_other_repo_dir. path ( ) . to_str ( ) . unwrap ( ) ,
483
+ "repo_2_commit" ,
484
+ )
485
+ . unwrap ( ) ;
486
+ let mut other_repo_commit_ids = Vec :: < CommitId > :: new ( ) ;
487
+ LogWalker :: new ( & other_repo)
488
+ . read ( & mut other_repo_commit_ids, 1 )
489
+ . unwrap ( ) ;
490
+
491
+ // Attempt a normal push,
492
+ // should fail as branches diverged
493
+ assert_eq ! (
494
+ push(
495
+ tmp_other_repo_dir. path( ) . to_str( ) . unwrap( ) ,
496
+ "origin" ,
497
+ "master" ,
498
+ false ,
499
+ None ,
500
+ None ,
501
+ )
502
+ . is_err( ) ,
503
+ true
504
+ ) ;
505
+
506
+ // Check that the other commit is not in upstream,
507
+ // a normal push would not rewrite history
508
+ let mut commit_ids = Vec :: < CommitId > :: new ( ) ;
509
+ LogWalker :: new ( & upstream) . read ( & mut commit_ids, 1 ) . unwrap ( ) ;
510
+ assert_eq ! ( commit_ids. contains( & repo_commit_ids[ 0 ] ) , true ) ;
511
+
512
+ // Attempt force push,
513
+ // should work as it forces the push through
514
+ assert_eq ! (
515
+ push(
516
+ tmp_other_repo_dir. path( ) . to_str( ) . unwrap( ) ,
517
+ "origin" ,
518
+ "master" ,
519
+ true ,
520
+ None ,
521
+ None ,
522
+ )
523
+ . is_err( ) ,
524
+ false
525
+ ) ;
526
+
527
+ commit_ids. clear ( ) ;
528
+ LogWalker :: new ( & upstream) . read ( & mut commit_ids, 1 ) . unwrap ( ) ;
529
+
530
+ // Check that only the other repo commit is now in upstream
531
+ assert_eq ! (
532
+ commit_ids. contains( & other_repo_commit_ids[ 0 ] ) ,
533
+ true
534
+ ) ;
535
+
536
+ assert_eq ! (
537
+ upstream
538
+ . find_commit( ( commit_ids[ 0 ] ) . into( ) )
539
+ . unwrap( )
540
+ . parents( )
541
+ . next( )
542
+ . unwrap( )
543
+ . id( )
544
+ == upstream_parent,
545
+ true
546
+ ) ;
547
+ }
309
548
}
0 commit comments