Skip to content

Commit bd0e881

Browse files
committed
checkout: introduce git_checkout_perfdata
Checkout can now provide performance data about the number of (some) syscalls performed using an optional callback. This structure remains internal-only in maintenance branches.
1 parent 04bdd97 commit bd0e881

File tree

5 files changed

+135
-50
lines changed

5 files changed

+135
-50
lines changed

src/checkout.c

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ typedef struct {
6767
bool reload_submodules;
6868
size_t total_steps;
6969
size_t completed_steps;
70+
git_checkout_perfdata perfdata;
7071
} checkout_data;
7172

7273
typedef struct {
@@ -1289,50 +1290,86 @@ static int checkout_get_actions(
12891290
return error;
12901291
}
12911292

1293+
static int checkout_mkdir(
1294+
checkout_data *data,
1295+
const char *path,
1296+
const char *base,
1297+
mode_t mode,
1298+
unsigned int flags)
1299+
{
1300+
struct git_futils_mkdir_perfdata mkdir_perfdata = {0};
1301+
1302+
int error = git_futils_mkdir_withperf(
1303+
path, base, mode, flags, &mkdir_perfdata);
1304+
1305+
data->perfdata.mkdir_calls += mkdir_perfdata.mkdir_calls;
1306+
data->perfdata.stat_calls += mkdir_perfdata.stat_calls;
1307+
data->perfdata.chmod_calls += mkdir_perfdata.chmod_calls;
1308+
1309+
return error;
1310+
}
1311+
1312+
static int mkpath2file(
1313+
checkout_data *data, const char *path, unsigned int mode)
1314+
{
1315+
return checkout_mkdir(
1316+
data, path, git_repository_workdir(data->repo), mode,
1317+
GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
1318+
}
1319+
12921320
static int buffer_to_file(
1321+
checkout_data *data,
12931322
struct stat *st,
12941323
git_buf *buf,
12951324
const char *path,
1296-
mode_t dir_mode,
1297-
int file_open_flags,
12981325
mode_t file_mode)
12991326
{
13001327
int error;
13011328

1302-
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
1329+
if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
13031330
return error;
13041331

13051332
if ((error = git_futils_writebuffer(
1306-
buf, path, file_open_flags, file_mode)) < 0)
1333+
buf, path, data->opts.file_open_flags, file_mode)) < 0)
13071334
return error;
13081335

1309-
if (st != NULL && (error = p_stat(path, st)) < 0)
1310-
giterr_set(GITERR_OS, "Error statting '%s'", path);
1336+
if (st) {
1337+
data->perfdata.stat_calls++;
1338+
1339+
if ((error = p_stat(path, st)) < 0) {
1340+
giterr_set(GITERR_OS, "Error statting '%s'", path);
1341+
return error;
1342+
}
1343+
}
13111344

1312-
else if (GIT_PERMS_IS_EXEC(file_mode) &&
1313-
(error = p_chmod(path, file_mode)) < 0)
1314-
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
1345+
if (GIT_PERMS_IS_EXEC(file_mode)) {
1346+
data->perfdata.chmod_calls++;
1347+
1348+
if ((error = p_chmod(path, file_mode)) < 0)
1349+
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
1350+
}
13151351

13161352
return error;
13171353
}
13181354

13191355
static int blob_content_to_file(
1356+
checkout_data *data,
13201357
struct stat *st,
13211358
git_blob *blob,
13221359
const char *path,
13231360
const char * hint_path,
1324-
mode_t entry_filemode,
1325-
git_checkout_options *opts)
1361+
mode_t entry_filemode)
13261362
{
1327-
int error = 0;
1328-
mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode;
1363+
mode_t file_mode = data->opts.file_mode ?
1364+
data->opts.file_mode : entry_filemode;
13291365
git_buf out = GIT_BUF_INIT;
13301366
git_filter_list *fl = NULL;
1367+
int error = 0;
13311368

13321369
if (hint_path == NULL)
13331370
hint_path = path;
13341371

1335-
if (!opts->disable_filters)
1372+
if (!data->opts.disable_filters)
13361373
error = git_filter_list_load(
13371374
&fl, git_blob_owner(blob), blob, hint_path,
13381375
GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT);
@@ -1343,9 +1380,7 @@ static int blob_content_to_file(
13431380
git_filter_list_free(fl);
13441381

13451382
if (!error) {
1346-
error = buffer_to_file(
1347-
st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode);
1348-
1383+
error = buffer_to_file(data, st, &out, path, file_mode);
13491384
st->st_mode = entry_filemode;
13501385

13511386
git_buf_free(&out);
@@ -1355,29 +1390,30 @@ static int blob_content_to_file(
13551390
}
13561391

13571392
static int blob_content_to_link(
1393+
checkout_data *data,
13581394
struct stat *st,
13591395
git_blob *blob,
1360-
const char *path,
1361-
mode_t dir_mode,
1362-
int can_symlink)
1396+
const char *path)
13631397
{
13641398
git_buf linktarget = GIT_BUF_INIT;
13651399
int error;
13661400

1367-
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
1401+
if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
13681402
return error;
13691403

13701404
if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
13711405
return error;
13721406

1373-
if (can_symlink) {
1407+
if (data->can_symlink) {
13741408
if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
13751409
giterr_set(GITERR_OS, "Could not create symlink %s\n", path);
13761410
} else {
13771411
error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
13781412
}
13791413

13801414
if (!error) {
1415+
data->perfdata.stat_calls++;
1416+
13811417
if ((error = p_lstat(path, st)) < 0)
13821418
giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
13831419

@@ -1421,6 +1457,7 @@ static int checkout_submodule_update_index(
14211457
if (git_buf_puts(&data->path, file->path) < 0)
14221458
return -1;
14231459

1460+
data->perfdata.stat_calls++;
14241461
if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
14251462
giterr_set(
14261463
GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
@@ -1442,7 +1479,8 @@ static int checkout_submodule(
14421479
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
14431480
return 0;
14441481

1445-
if ((error = git_futils_mkdir(
1482+
if ((error = checkout_mkdir(
1483+
data,
14461484
file->path, data->opts.target_directory,
14471485
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
14481486
return error;
@@ -1481,10 +1519,13 @@ static void report_progress(
14811519
data->opts.progress_payload);
14821520
}
14831521

1484-
static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)
1522+
static int checkout_safe_for_update_only(
1523+
checkout_data *data, const char *path, mode_t expected_mode)
14851524
{
14861525
struct stat st;
14871526

1527+
data->perfdata.stat_calls++;
1528+
14881529
if (p_lstat(path, &st) < 0) {
14891530
/* if doesn't exist, then no error and no update */
14901531
if (errno == ENOENT || errno == ENOTDIR)
@@ -1517,11 +1558,9 @@ static int checkout_write_content(
15171558
return error;
15181559

15191560
if (S_ISLNK(mode))
1520-
error = blob_content_to_link(
1521-
st, blob, full_path, data->opts.dir_mode, data->can_symlink);
1561+
error = blob_content_to_link(data, st, blob, full_path);
15221562
else
1523-
error = blob_content_to_file(
1524-
st, blob, full_path, hint_path, mode, &data->opts);
1563+
error = blob_content_to_file(data, st, blob, full_path, hint_path, mode);
15251564

15261565
git_blob_free(blob);
15271566

@@ -1552,7 +1591,7 @@ static int checkout_blob(
15521591

15531592
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
15541593
int rval = checkout_safe_for_update_only(
1555-
git_buf_cstr(&data->path), file->mode);
1594+
data, git_buf_cstr(&data->path), file->mode);
15561595
if (rval <= 0)
15571596
return rval;
15581597
}
@@ -1807,7 +1846,7 @@ static int checkout_write_entry(
18071846
}
18081847

18091848
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
1810-
(error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0)
1849+
(error = checkout_safe_for_update_only(data, git_buf_cstr(&data->path), side->mode)) <= 0)
18111850
return error;
18121851

18131852
return checkout_write_content(data,
@@ -1906,7 +1945,7 @@ static int checkout_write_merge(
19061945
goto done;
19071946

19081947
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
1909-
(error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0)
1948+
(error = checkout_safe_for_update_only(data, git_buf_cstr(&path_workdir), result.mode)) <= 0)
19101949
goto done;
19111950

19121951
if (!data->opts.disable_filters) {
@@ -1922,7 +1961,7 @@ static int checkout_write_merge(
19221961
out_data.size = result.len;
19231962
}
19241963

1925-
if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 ||
1964+
if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 ||
19261965
(error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
19271966
(error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 ||
19281967
(error = git_filebuf_commit(&output)) < 0)
@@ -2157,8 +2196,9 @@ static int checkout_data_init(
21572196
if (!data->opts.target_directory)
21582197
data->opts.target_directory = git_repository_workdir(repo);
21592198
else if (!git_path_isdir(data->opts.target_directory) &&
2160-
(error = git_futils_mkdir(data->opts.target_directory, NULL,
2161-
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
2199+
(error = checkout_mkdir(data,
2200+
data->opts.target_directory, NULL,
2201+
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
21622202
goto cleanup;
21632203

21642204
/* refresh config and index content unless NO_REFRESH is given */

src/checkout.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
1414

15+
/** Internal structure; this is exposed in future versions. */
16+
typedef struct {
17+
size_t mkdir_calls;
18+
size_t stat_calls;
19+
size_t chmod_calls;
20+
} git_checkout_perfdata;
21+
1522
/**
1623
* Update the working directory to match the target iterator. The
1724
* expected baseline value can be passed in via the checkout options

src/fileops.c

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,12 @@ void git_futils_mmap_free(git_map *out)
279279
p_munmap(out);
280280
}
281281

282-
int git_futils_mkdir(
282+
int git_futils_mkdir_withperf(
283283
const char *path,
284284
const char *base,
285285
mode_t mode,
286-
uint32_t flags)
286+
uint32_t flags,
287+
struct git_futils_mkdir_perfdata *perfdata)
287288
{
288289
int error = -1;
289290
git_buf make_path = GIT_BUF_INIT;
@@ -352,15 +353,20 @@ int git_futils_mkdir(
352353
st.st_mode = 0;
353354

354355
/* make directory */
356+
perfdata->mkdir_calls++;
357+
355358
if (p_mkdir(make_path.ptr, mode) < 0) {
356359
int tmp_errno = giterr_system_last();
357360

358361
/* ignore error if not at end or if directory already exists */
359-
if (lastch == '\0' &&
360-
(p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
361-
giterr_system_set(tmp_errno);
362-
giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
363-
goto done;
362+
if (lastch == '\0') {
363+
perfdata->stat_calls++;
364+
365+
if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
366+
giterr_system_set(tmp_errno);
367+
giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
368+
goto done;
369+
}
364370
}
365371

366372
/* with exclusive create, existing dir is an error */
@@ -374,29 +380,46 @@ int git_futils_mkdir(
374380
/* chmod if requested and necessary */
375381
if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
376382
(lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
377-
st.st_mode != mode &&
378-
(error = p_chmod(make_path.ptr, mode)) < 0 &&
379-
lastch == '\0') {
380-
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
381-
goto done;
383+
st.st_mode != mode) {
384+
385+
perfdata->chmod_calls++;
386+
387+
if ((error = p_chmod(make_path.ptr, mode)) < 0 &&
388+
lastch == '\0') {
389+
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
390+
goto done;
391+
}
382392
}
383393
}
384394

385395
error = 0;
386396

387397
/* check that full path really is a directory if requested & needed */
388398
if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
389-
lastch != '\0' &&
390-
(p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
391-
giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
392-
error = GIT_ENOTFOUND;
399+
lastch != '\0') {
400+
perfdata->stat_calls++;
401+
402+
if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
403+
giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
404+
error = GIT_ENOTFOUND;
405+
}
393406
}
394407

395408
done:
396409
git_buf_free(&make_path);
397410
return error;
398411
}
399412

413+
int git_futils_mkdir(
414+
const char *path,
415+
const char *base,
416+
mode_t mode,
417+
uint32_t flags)
418+
{
419+
struct git_futils_mkdir_perfdata perfdata = {0};
420+
return git_futils_mkdir_withperf(path, base, mode, flags, &perfdata);
421+
}
422+
400423
int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
401424
{
402425
return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);

src/fileops.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ typedef enum {
8484
GIT_MKDIR_VERIFY_DIR = 64,
8585
} git_futils_mkdir_flags;
8686

87+
struct git_futils_mkdir_perfdata
88+
{
89+
size_t stat_calls;
90+
size_t mkdir_calls;
91+
size_t chmod_calls;
92+
};
93+
8794
/**
8895
* Create a directory or entire path.
8996
*
@@ -95,8 +102,15 @@ typedef enum {
95102
* @param base Root for relative path. These directories will never be made.
96103
* @param mode The mode to use for created directories.
97104
* @param flags Combination of the mkdir flags above.
105+
* @param perfdata Performance data, use `git_futils_mkdir` if you don't want this data.
98106
* @return 0 on success, else error code
99107
*/
108+
extern int git_futils_mkdir_withperf(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_perfdata *perfdata);
109+
110+
/**
111+
* Create a directory or entire path. Similar to `git_futils_mkdir_withperf`
112+
* without performance data.
113+
*/
100114
extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);
101115

102116
/**

tests/checkout/tree.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,3 +1101,4 @@ void test_checkout_tree__case_changing_rename(void)
11011101
git_commit_free(dir_commit);
11021102
git_commit_free(master_commit);
11031103
}
1104+

0 commit comments

Comments
 (0)