Commit 99b68a2aecfaa24f252f265d61b230b8e2576dd2

Carlos Martín Nieto 2015-03-03T13:47:13

Merge pull request #2908 from ethomson/safe_create Allow checkout to handle newly cloned repositories, remove `GIT_CHECKOUT_SAFE_CREATE`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bffcb25..6a81a20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@ v0.22 + 1
 
 * Rename and copy detection is enabled for small files.
 
+* Checkout can now handle an initial checkout of a repository, making
+  `GIT_CHECKOUT_SAFE_CREATE` unnecessary for users of clone.
+
 ### API additions
 
 * Parsing and retrieving a configuration value as a path is exposed
@@ -18,6 +21,11 @@ v0.22 + 1
 
 ### Breaking API changes
 
+* `GIT_CHECKOUT_SAFE_CREATE` has been removed.  Most users will generally
+  be able to switch to `GIT_CHECKOUT_SAFE`, but if you require missing
+  file handling during checkout, you may now use `GIT_CHECKOUT_SAFE |
+  GIT_CHECKOUT_RECREATE_MISSING`.
+
 v0.22
 ------
 
diff --git a/examples/network/clone.c b/examples/network/clone.c
index 6144e21..270bb68 100644
--- a/examples/network/clone.c
+++ b/examples/network/clone.c
@@ -82,7 +82,7 @@ int do_clone(git_repository *repo, int argc, char **argv)
 	}
 
 	// Set up options
-	checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 	checkout_opts.progress_cb = checkout_progress;
 	checkout_opts.progress_payload = &pd;
 	clone_opts.checkout_opts = checkout_opts;
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 4fe1340..ed39bd3 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -31,7 +31,7 @@ GIT_BEGIN_DECL
  * check out, the "baseline" tree of what was checked out previously, the
  * working directory for actual files, and the index for staged changes.
  *
- * You give checkout one of four strategies for update:
+ * You give checkout one of three strategies for update:
  *
  * - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
  *   etc., but doesn't make any actual changes.
@@ -40,8 +40,8 @@ GIT_BEGIN_DECL
  *   make the working directory match the target (including potentially
  *   discarding modified files).
  *
- * In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE`
- * both of which only make modifications that will not lose changes.
+ * - `GIT_CHECKOUT_SAFE` is between these two options, it will only make
+ *   modifications that will not lose changes.
  *
  *                         |  target == baseline   |  target != baseline  |
  *    ---------------------|-----------------------|----------------------|
@@ -51,28 +51,21 @@ GIT_BEGIN_DECL
  *     workdir exists and  |       no action       |   conflict (notify   |
  *       is != baseline    | notify dirty MODIFIED | and cancel checkout) |
  *    ---------------------|-----------------------|----------------------|
- *      workdir missing,   | create if SAFE_CREATE |     create file      |
- *      baseline present   | notify dirty DELETED  |                      |
+ *      workdir missing,   | notify dirty DELETED  |     create file      |
+ *      baseline present   |                       |                      |
  *    ---------------------|-----------------------|----------------------|
  *
- * The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE
- * will cause a file to be checked out if it is missing from the working
- * directory even if it is not modified between the target and baseline.
- *
- *
  * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
  * notification callback (see below) that displays information about dirty
  * files.  The default behavior will cancel checkout on conflicts.
  *
- * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
+ * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE` with a
  * notification callback that cancels the operation if a dirty-but-existing
  * file is found in the working directory.  This core git command isn't
  * quite "force" but is sensitive about some types of changes.
  *
  * To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
  *
- * To emulate `git clone` use `GIT_CHECKOUT_SAFE_CREATE` in the options.
- *
  *
  * There are some additional flags to modified the behavior of checkout:
  *
@@ -116,12 +109,12 @@ typedef enum {
 	/** Allow safe updates that cannot overwrite uncommitted data */
 	GIT_CHECKOUT_SAFE = (1u << 0),
 
-	/** Allow safe updates plus creation of missing files */
-	GIT_CHECKOUT_SAFE_CREATE = (1u << 1),
-
 	/** Allow all updates to force working directory to look like index */
-	GIT_CHECKOUT_FORCE = (1u << 2),
+	GIT_CHECKOUT_FORCE = (1u << 1),
+
 
+	/** Allow checkout to recreate missing files */
+	GIT_CHECKOUT_RECREATE_MISSING = (1u << 2),
 
 	/** Allow checkout to make safe updates even if conflicts are found */
 	GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
diff --git a/include/git2/clone.h b/include/git2/clone.h
index 4644085..1cee516 100644
--- a/include/git2/clone.h
+++ b/include/git2/clone.h
@@ -106,9 +106,7 @@ typedef struct git_clone_options {
 	/**
 	 * These options are passed to the checkout step. To disable
 	 * checkout, set the `checkout_strategy` to
-	 * `GIT_CHECKOUT_NONE`. Generally you will want the use
-	 * GIT_CHECKOUT_SAFE_CREATE to create all files in the working
-	 * directory for the newly cloned repository.
+	 * `GIT_CHECKOUT_NONE`.
 	 */
 	git_checkout_options checkout_opts;
 
@@ -173,7 +171,9 @@ typedef struct git_clone_options {
 } git_clone_options;
 
 #define GIT_CLONE_OPTIONS_VERSION 1
-#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE_CREATE}, GIT_REMOTE_CALLBACKS_INIT}
+#define GIT_CLONE_OPTIONS_INIT { GIT_CLONE_OPTIONS_VERSION, \
+	{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
+	GIT_REMOTE_CALLBACKS_INIT }
 
 /**
  * Initializes a `git_clone_options` with default values. Equivalent to
diff --git a/include/git2/submodule.h b/include/git2/submodule.h
index f03ea2d..1c139d0 100644
--- a/include/git2/submodule.h
+++ b/include/git2/submodule.h
@@ -137,7 +137,7 @@ typedef struct git_submodule_update_options {
 
 	/**
 	 * The checkout strategy to use when the sub repository needs to
-	 * be cloned. Use GIT_CHECKOUT_SAFE_CREATE to create all files
+	 * be cloned. Use GIT_CHECKOUT_SAFE to create all files
 	 * in the working directory for the newly cloned repository.
 	 */
 	unsigned int clone_checkout_strategy;
@@ -152,8 +152,8 @@ typedef struct git_submodule_update_options {
 #define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1
 #define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \
 	{ GIT_CHECKOUT_OPTIONS_VERSION, \
-		{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE}, \
-	GIT_REMOTE_CALLBACKS_INIT, GIT_CHECKOUT_SAFE_CREATE }
+		{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
+	GIT_REMOTE_CALLBACKS_INIT, GIT_CHECKOUT_SAFE }
 
 /**
  * Initializes a `git_submodule_update_options` with default values.
diff --git a/src/.checkout.c.swp b/src/.checkout.c.swp
new file mode 100644
index 0000000..d946ab5
Binary files /dev/null and b/src/.checkout.c.swp differ
diff --git a/src/checkout.c b/src/checkout.c
index f71be26..c069281 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -255,13 +255,13 @@ static int checkout_action_no_wd(
 		error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL);
 		if (error)
 			return error;
-		*action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
+		*action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE);
 		break;
 	case GIT_DELTA_ADDED:    /* case 2 or 28 (and 5 but not really) */
 		*action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
 		break;
 	case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
-		*action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
+		*action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT);
 		break;
 	case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
 		if (delta->new_file.mode == GIT_FILEMODE_TREE)
@@ -2346,11 +2346,17 @@ static int checkout_data_init(
 		}
 	}
 
-	/* if you are forcing, definitely allow safe updates */
+	/* if you are forcing, allow all safe updates, plus recreate missing */
 	if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
-		data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
-	if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
-		data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+		data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE |
+			GIT_CHECKOUT_RECREATE_MISSING;
+
+	/* if the repository does not actually have an index file, then this
+	 * is an initial checkout (perhaps from clone), so we allow safe updates
+	 */
+	if (!data->index->on_disk &&
+		(data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0)
+		data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING;
 
 	data->strategy = data->opts.checkout_strategy;
 
diff --git a/src/cherrypick.c b/src/cherrypick.c
index ebc9fcd..c929751 100644
--- a/src/cherrypick.c
+++ b/src/cherrypick.c
@@ -72,7 +72,7 @@ static int cherrypick_normalize_opts(
 	const char *their_label)
 {
 	int error = 0;
-	unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE_CREATE |
+	unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
 		GIT_CHECKOUT_ALLOW_CONFLICTS;
 
 	GIT_UNUSED(repo);
diff --git a/src/revert.c b/src/revert.c
index f8a7f43..c481e7d 100644
--- a/src/revert.c
+++ b/src/revert.c
@@ -73,7 +73,7 @@ static int revert_normalize_opts(
 	const char *their_label)
 {
 	int error = 0;
-	unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE_CREATE |
+	unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
 		GIT_CHECKOUT_ALLOW_CONFLICTS;
 
 	GIT_UNUSED(repo);
diff --git a/tests/checkout/.icase.c.swp b/tests/checkout/.icase.c.swp
new file mode 100644
index 0000000..facd136
Binary files /dev/null and b/tests/checkout/.icase.c.swp differ
diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c
index b6d4e94..ecbcc8a 100644
--- a/tests/checkout/crlf.c
+++ b/tests/checkout/crlf.c
@@ -21,7 +21,7 @@ void test_checkout_crlf__cleanup(void)
 void test_checkout_crlf__detect_crlf_autocrlf_false(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
 
@@ -36,7 +36,7 @@ void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void)
 	git_index *index;
 	const git_index_entry *entry;
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
 
@@ -56,7 +56,7 @@ void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void)
 void test_checkout_crlf__detect_crlf_autocrlf_true(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -73,7 +73,7 @@ void test_checkout_crlf__detect_crlf_autocrlf_true(void)
 void test_checkout_crlf__more_lf_autocrlf_true(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -85,7 +85,7 @@ void test_checkout_crlf__more_lf_autocrlf_true(void)
 void test_checkout_crlf__more_crlf_autocrlf_true(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -97,7 +97,7 @@ void test_checkout_crlf__more_crlf_autocrlf_true(void)
 void test_checkout_crlf__all_crlf_autocrlf_true(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -109,7 +109,7 @@ void test_checkout_crlf__all_crlf_autocrlf_true(void)
 void test_checkout_crlf__detect_crlf_autocrlf_true_utf8(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -136,7 +136,7 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void)
 	git_index *index;
 	const git_index_entry *entry;
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -162,7 +162,7 @@ void test_checkout_crlf__with_ident(void)
 	git_index *index;
 	git_blob *blob;
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_git_mkfile("crlf/.gitattributes",
 		"*.txt text\n*.bin binary\n"
@@ -252,7 +252,7 @@ void test_checkout_crlf__with_ident(void)
 void test_checkout_crlf__autocrlf_false_no_attrs(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
 
@@ -265,7 +265,7 @@ void test_checkout_crlf__autocrlf_false_no_attrs(void)
 void test_checkout_crlf__autocrlf_true_no_attrs(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
@@ -283,7 +283,7 @@ void test_checkout_crlf__autocrlf_true_no_attrs(void)
 void test_checkout_crlf__autocrlf_input_no_attrs(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_repo_set_string(g_repo, "core.autocrlf", "input");
 
@@ -296,7 +296,7 @@ void test_checkout_crlf__autocrlf_input_no_attrs(void)
 void test_checkout_crlf__autocrlf_false_text_auto_attr(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
 
@@ -316,7 +316,7 @@ void test_checkout_crlf__autocrlf_false_text_auto_attr(void)
 void test_checkout_crlf__autocrlf_true_text_auto_attr(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
 
@@ -336,7 +336,7 @@ void test_checkout_crlf__autocrlf_true_text_auto_attr(void)
 void test_checkout_crlf__autocrlf_input_text_auto_attr(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 
 	cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
 
diff --git a/tests/checkout/index.c b/tests/checkout/index.c
index 112324a..63ed4b1 100644
--- a/tests/checkout/index.c
+++ b/tests/checkout/index.c
@@ -49,7 +49,7 @@ void test_checkout_index__can_create_missing_files(void)
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -69,7 +69,9 @@ void test_checkout_index__can_remove_untracked_files(void)
 	cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
 
 	opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+		GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING |
+		GIT_CHECKOUT_REMOVE_UNTRACKED;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -88,7 +90,7 @@ void test_checkout_index__honor_the_specified_pathspecs(void)
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -107,7 +109,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
 	cl_git_mkfile("./testrepo/.gitattributes", attributes);
 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -125,7 +127,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
 	cl_git_pass(p_unlink("./testrepo/.gitattributes"));
 	cl_repo_set_bool(g_repo, "core.autocrlf", true);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -139,7 +141,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
 
 	cl_repo_set_bool(g_repo, "core.symlinks", true);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -165,7 +167,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
 
 	cl_repo_set_bool(g_repo, "core.symlinks", false);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -207,7 +209,7 @@ void test_checkout_index__options_disable_filters(void)
 
 	cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 	opts.disable_filters = false;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
@@ -238,7 +240,7 @@ void test_checkout_index__options_dir_modes(void)
 
 	reset_index_to_treeish((git_object *)commit);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 	opts.dir_mode = 0701;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
@@ -264,7 +266,7 @@ void test_checkout_index__options_override_file_modes(void)
 	if (!cl_is_chmod_supported())
 		return;
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 	opts.file_mode = 0700;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
@@ -329,8 +331,9 @@ void test_checkout_index__can_notify_of_skipped_files(void)
 	data.file = "new.txt";
 	data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
 
-	opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING |
+		GIT_CHECKOUT_ALLOW_CONFLICTS;
 	opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
 	opts.notify_cb = test_checkout_notify_cb;
 	opts.notify_payload = &data;
@@ -368,7 +371,9 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
 	cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
 
 	opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+		GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING |
+		GIT_CHECKOUT_ALLOW_CONFLICTS;
 	opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
 	opts.notify_cb = dont_notify_cb;
 	opts.notify_payload = NULL;
@@ -388,7 +393,7 @@ void test_checkout_index__calls_progress_callback(void)
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
 	int calls = 0;
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 	opts.progress_cb = checkout_progress_counter;
 	opts.progress_payload = &calls;
 
@@ -423,7 +428,9 @@ void test_checkout_index__can_overcome_name_clashes(void)
 	cl_assert(git_path_isfile("./testrepo/path0/file0"));
 
 	opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+		GIT_CHECKOUT_SAFE | 
+		GIT_CHECKOUT_RECREATE_MISSING |
+		GIT_CHECKOUT_ALLOW_CONFLICTS;
 	cl_git_pass(git_checkout_index(g_repo, index, &opts));
 
 	cl_assert(git_path_isfile("./testrepo/path1"));
@@ -473,7 +480,9 @@ void test_checkout_index__can_update_prefixed_files(void)
 	cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777));
 
 	opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+		GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING |
+		GIT_CHECKOUT_REMOVE_UNTRACKED;
 
 	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
 
@@ -523,7 +532,8 @@ void test_checkout_index__target_directory(void)
 	checkout_counts cts;
 	memset(&cts, 0, sizeof(cts));
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING;
 	opts.target_directory = "alternative";
 	cl_assert(!git_path_isdir("alternative"));
 
@@ -568,7 +578,8 @@ void test_checkout_index__target_directory_from_bare(void)
 	cl_git_pass(git_index_write(index));
 	git_index_free(index);
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING;
 
 	opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
 	opts.notify_cb = checkout_count_callback;
@@ -606,7 +617,7 @@ void test_checkout_index__can_get_repo_from_index(void)
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
 	cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
 
 	cl_git_pass(git_repository_index(&index, g_repo));
 
diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c
index 0fabadc..7ba9c25 100644
--- a/tests/checkout/tree.c
+++ b/tests/checkout/tree.c
@@ -15,7 +15,7 @@ void test_checkout_tree__initialize(void)
 	g_repo = cl_git_sandbox_init("testrepo");
 
 	GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTIONS_VERSION);
-	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
 }
 
 void test_checkout_tree__cleanup(void)
@@ -408,7 +408,7 @@ void test_checkout_tree__can_checkout_with_pattern(void)
 
 	/* now to a narrow patterned checkout */
 
-	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 	g_opts.paths.strings = entries;
 	g_opts.paths.count = 1;
 
@@ -445,7 +445,8 @@ void test_checkout_tree__can_disable_pattern_match(void)
 	/* now to a narrow patterned checkout, but disable pattern */
 
 	g_opts.checkout_strategy =
-		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
+		GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
 	g_opts.paths.strings = entries;
 	g_opts.paths.count = 1;
 
@@ -457,7 +458,7 @@ void test_checkout_tree__can_disable_pattern_match(void)
 
 	/* let's try that again, but allow the pattern match */
 
-	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 
 	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
 
@@ -824,7 +825,8 @@ void test_checkout_tree__target_directory_from_bare(void)
 	g_repo = cl_git_sandbox_init("testrepo.git");
 	cl_assert(git_repository_is_bare(g_repo));
 
-	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE |
+		GIT_CHECKOUT_RECREATE_MISSING;
 
 	opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
 	opts.notify_cb = checkout_count_callback;
@@ -1264,3 +1266,39 @@ void test_checkout_tree__can_update_but_not_write_index(void)
 	git_object_free(head);
 	git_index_free(index);
 }
+
+/* Emulate checking out in a repo created by clone --no-checkout,
+ * which would not have written an index. */
+void test_checkout_tree__safe_proceeds_if_no_index(void)
+{
+	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_oid oid;
+	git_object *obj = NULL;
+
+	assert_on_branch(g_repo, "master");
+	cl_must_pass(p_unlink("testrepo/.git/index"));
+
+	/* do second checkout safe because we should be clean after first */
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
+	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
+
+	cl_assert(git_path_isfile("testrepo/README"));
+	cl_assert(git_path_isfile("testrepo/branch_file.txt"));
+	cl_assert(git_path_isfile("testrepo/new.txt"));
+	cl_assert(git_path_isfile("testrepo/ab/4.txt"));
+	cl_assert(git_path_isfile("testrepo/ab/c/3.txt"));
+	cl_assert(git_path_isfile("testrepo/ab/de/2.txt"));
+	cl_assert(git_path_isfile("testrepo/ab/de/fgh/1.txt"));
+
+	cl_assert(!git_path_isdir("testrepo/a"));
+
+	assert_on_branch(g_repo, "subtrees");
+
+	git_object_free(obj);
+}
+
diff --git a/tests/checkout/typechange.c b/tests/checkout/typechange.c
index 7aa14b3..c323309 100644
--- a/tests/checkout/typechange.c
+++ b/tests/checkout/typechange.c
@@ -212,7 +212,7 @@ void test_checkout_typechange__checkout_with_conflicts(void)
 		p_mkdir("typechanges/d", 0777); /* intentionally empty dir */
 		force_create_file("typechanges/untracked");
 
-		opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+		opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 		memset(&cts, 0, sizeof(cts));
 
 		cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c
index a0264b0..ac7c192 100644
--- a/tests/clone/nonetwork.c
+++ b/tests/clone/nonetwork.c
@@ -22,7 +22,7 @@ void test_clone_nonetwork__initialize(void)
 	memset(&g_options, 0, sizeof(git_clone_options));
 	g_options.version = GIT_CLONE_OPTIONS_VERSION;
 	g_options.checkout_opts = dummy_opts;
-	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 	g_options.remote_callbacks = dummy_callbacks;
 	cl_git_pass(git_signature_now(&g_options.signature, "Me", "foo@example.com"));
 }
diff --git a/tests/online/clone.c b/tests/online/clone.c
index cae7b33..3bb9279 100644
--- a/tests/online/clone.c
+++ b/tests/online/clone.c
@@ -104,7 +104,7 @@ void test_online_clone__can_checkout_a_cloned_repo(void)
 	bool checkout_progress_cb_was_called = false,
 		  fetch_progress_cb_was_called = false;
 
-	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 	g_options.checkout_opts.progress_cb = &checkout_progress;
 	g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
 	g_options.remote_callbacks.transfer_progress = &fetch_progress;
diff --git a/tests/perf/helper__perf__do_merge.c b/tests/perf/helper__perf__do_merge.c
index 0022185..4c41f92 100644
--- a/tests/perf/helper__perf__do_merge.c
+++ b/tests/perf/helper__perf__do_merge.c
@@ -26,7 +26,7 @@ void perf__do_merge(const char *fixture,
 
 	perf__timer__start(&t_total);
 
-	checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 	clone_opts.checkout_opts = checkout_opts;
 	cl_git_pass(git_signature_now(&clone_opts.signature, "Me", "foo@example.com"));